Compare commits
87 Commits
9f69833a9d
...
responsive
Author | SHA1 | Date | |
---|---|---|---|
0b9b30f6c2 | |||
e4f4a0ba26 | |||
db56e011eb | |||
54bd9ad9ec | |||
3d672ce033 | |||
2c93d9199e | |||
621ad15006 | |||
345a62ea47 | |||
cdb82fe827 | |||
7f80d0615c | |||
2913703074 | |||
777d33a4bb | |||
0cdf1e297b | |||
b09aa47d06 | |||
7b00d49d43 | |||
62f6268651 | |||
0387793868 | |||
ed1acf8cc8 | |||
bd04a9be19 | |||
77067c7e01 | |||
ccdaea6d80 | |||
836463ad92 | |||
54ec74c6ed | |||
e55fa73d92 | |||
4569060d41 | |||
a7c7d31552 | |||
0536a3aeb7 | |||
a0dc421189 | |||
cf18f89e10 | |||
6379ce0041 | |||
07f3be8083 | |||
3de856f413 | |||
59484d8363 | |||
a6bd281946 | |||
049ba826dc | |||
b1b57c7b6a | |||
bfb31ba036 | |||
e0e09bcea1 | |||
8100e05cc3 | |||
0aee314908 | |||
779cce9f9d | |||
12baf5df2e | |||
dfa3989409 | |||
e3061b9f5b | |||
7582ac5218 | |||
2039c3d886 | |||
08021fa945 | |||
948fab0bb9 | |||
a6f3143466 | |||
859272b2a5 | |||
ea72be3b17 | |||
2707a856d9 | |||
35a5e436c6 | |||
569b541eec | |||
26821b48ef | |||
fcc91a17d4 | |||
69d7cd1da6 | |||
aa60dc5e32 | |||
2cb641cebb | |||
9d84151ee5 | |||
1265d3d4a9 | |||
07ddc100c0 | |||
efcac82d6a | |||
920ec5ab9a | |||
3d4020d2ae | |||
3140ef0b72 | |||
bd2b505a5e | |||
9d599f9bd4 | |||
ffbe6918f8 | |||
be4cbac363 | |||
790d9fcbba | |||
9d23167be7 | |||
d3b1b3a020 | |||
01ab774c48 | |||
1b8af40052 | |||
25529178d0 | |||
0e991b1dee | |||
f48cfb997c | |||
d7dadd01d7 | |||
5e24e8f234 | |||
75eb02a75a | |||
2a4e7c719b | |||
96740f9809 | |||
b76f1cd53d | |||
ca908fdf0f | |||
85a5bde512 | |||
d51a0dd6ca |
@ -8,6 +8,9 @@ No line breaks in inline code fences
|
|||||||
Style inline code fences with background
|
Style inline code fences with background
|
||||||
Integrate ins plugin
|
Integrate ins plugin
|
||||||
|
|
||||||
|
Fediring?
|
||||||
|
Add "Now" to RSS feed.
|
||||||
|
|
||||||
Performance / Accessibility:
|
Performance / Accessibility:
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
Lazy load images
|
Lazy load images
|
||||||
|
39
README.md
39
README.md
@ -1,6 +1,37 @@
|
|||||||
# nathanupchurch.com - Based on eleventy-base-blog v8
|
# My 11ty Blog
|
||||||
|
My blog, originally based on the very helpful eleventy-base-blog v8, although it has come a long way from its humble beginnings. For documentation, check [the wiki](https://upchur.ch/gitea/n_u/nathanupchurch.com/wiki).
|
||||||
|
|
||||||
### Implementation Notes
|
## Features
|
||||||
|
### Design
|
||||||
|
* Fluid type and spacing systems for responsive pages with zero breakpoints
|
||||||
|
* Dark mode
|
||||||
|
* Graceful but unobtrusive page transitions
|
||||||
|
* Pretty variable typefaces
|
||||||
|
* Pretty 401 and 403 error pages
|
||||||
|
|
||||||
- `content/blog/` has the blog posts but really they can live in any directory. They need only the `posts` tag to be included in the blog posts [collection](https://www.11ty.dev/docs/collections/).
|
### Fediverse Integration
|
||||||
- Use the `eleventyNavigation` key (via the [Eleventy Navigation plugin](https://www.11ty.dev/docs/plugins/navigation/)) in your front matter to add a template to the top level site navigation. This is in use on `content/index.njk` and `content/about/index.md`.
|
* Mastodon [toot embedding](https://upchur.ch/gitea/n_u/nathanupchurch.com/wiki/Home#embed-a-toot-from-mastodon-using-the-toot-shortcode)
|
||||||
|
* [Commenting](https://upchur.ch/gitea/n_u/nathanupchurch.com/wiki/Home#adding-comments-via-mastodon) via Mastodon
|
||||||
|
|
||||||
|
### Indieweb
|
||||||
|
* [Auto-generated linktree-style page](https://upchur.ch/gitea/n_u/nathanupchurch.com/wiki#me) for the blog owner with support for custom attributes such as: `rel="me"`
|
||||||
|
* Built in support for [webring links](https://upchur.ch/gitea/n_u/nathanupchurch.com/wiki#webrings)
|
||||||
|
* Auto-generated, **styled** RSS feeds
|
||||||
|
* All blog posts
|
||||||
|
* Each individual tag
|
||||||
|
* /now page that nicely handles posts tagged with "now"
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
* Reusable web components:
|
||||||
|
* Card
|
||||||
|
* Mastodon comment
|
||||||
|
* Profile picture
|
||||||
|
* Embedded toot
|
||||||
|
|
||||||
|
### Quality of Life
|
||||||
|
* Copyright notice, default post image, alt text, and author details defined in `metadata.js`.
|
||||||
|
* "Read Next" highlighting the previous blog post at the bottom of every post
|
||||||
|
* robots.txt tells AI scrapers to GTFO
|
||||||
|
|
||||||
|
### Weird and Wonderful
|
||||||
|
* [Accessible cowsay output embedding](https://upchur.ch/gitea/n_u/nathanupchurch.com/wiki#add-a-cowsay-to-a-post)
|
||||||
|
24
_data/cowList.js
Normal file
24
_data/cowList.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export default {
|
||||||
|
onScience: `
|
||||||
|
_________________________________________
|
||||||
|
( Once, when the secrets of science were )
|
||||||
|
( the jealously guarded property of a )
|
||||||
|
( small priesthood, the common man had no )
|
||||||
|
( hope of mastering their arcane )
|
||||||
|
( complexities. Years of study in musty )
|
||||||
|
( classrooms were prerequisite to )
|
||||||
|
( obtaining even a dim, incoherent )
|
||||||
|
( knowledge of science. )
|
||||||
|
( )
|
||||||
|
( Today all that has changed: a dim, )
|
||||||
|
( incoherent knowledge of science is )
|
||||||
|
( available to anyone. )
|
||||||
|
( )
|
||||||
|
( -- Tom Weller, "Science Made Stupid" )
|
||||||
|
-----------------------------------------
|
||||||
|
o ^__^
|
||||||
|
o (oo)\\_______
|
||||||
|
(__)\\ )\\/\\
|
||||||
|
||----w |
|
||||||
|
|| ||`
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
title: "Nathan Upchurch",
|
title: "Nathan Upchurch",
|
||||||
logo: "/img/logo_favicon.svg",
|
logo: "/img/logo.svg",
|
||||||
url: "https://nathanupchurch.com/",
|
url: "https://nathanupchurch.com/",
|
||||||
language: "en",
|
language: "en",
|
||||||
description: "The personal website and blog of Nathan Upchurch.",
|
description: "The personal website and blog of Nathan Upchurch.",
|
||||||
@ -8,16 +8,20 @@ module.exports = {
|
|||||||
name: "Nathan Upchurch",
|
name: "Nathan Upchurch",
|
||||||
email: "blog@upchur.ch",
|
email: "blog@upchur.ch",
|
||||||
url: "https://nathanupchurch.com/me",
|
url: "https://nathanupchurch.com/me",
|
||||||
profilePic: "/img/CN20191025_301_Srt_SQUARE.jpg"
|
profilePic: "/img/CN20191025_301_Srt_SQUARE_crop.jpg"
|
||||||
},
|
},
|
||||||
|
copyrightNotice: "© Nathan Upchurch 2022 - 2024",
|
||||||
defaultPostImageURL: "/img/vasilina-sirotina-1NMPvajSt9Q-unsplash_copy.avif",
|
defaultPostImageURL: "/img/vasilina-sirotina-1NMPvajSt9Q-unsplash_copy.avif",
|
||||||
defaultPostImageAlt: "The default post image: a close picture of the dark green leaves of a plant.",
|
defaultPostImageAlt: "The default post image: a close picture of the dark green leaves of a plant.",
|
||||||
|
mastodonHost: "lounge.town",
|
||||||
|
mastodonUser: "nathanu",
|
||||||
|
postlistHeaderText: "Latest posts from the blog:",
|
||||||
socialLinks: [
|
socialLinks: [
|
||||||
{
|
{
|
||||||
title: "My Blog",
|
title: "My Blog",
|
||||||
linkURL: "https://nathanupchurch.com",
|
linkURL: "https://nathanupchurch.com",
|
||||||
linkDisplay: "My Blog",
|
linkDisplay: "My Blog",
|
||||||
iconURL: "/img/logo_favicon_dark.svg"
|
iconURL: "/img/logo.svg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Mastodon",
|
title: "Mastodon",
|
||||||
@ -68,5 +72,19 @@ module.exports = {
|
|||||||
linkDisplay: "Keyoxide Identity Profile",
|
linkDisplay: "Keyoxide Identity Profile",
|
||||||
iconURL: "/img/keyoxide.svg"
|
iconURL: "/img/keyoxide.svg"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
webrings: [
|
||||||
|
{
|
||||||
|
name: "Fediring",
|
||||||
|
ringURL: "https://fediring.net/",
|
||||||
|
previousURL: "https://fediring.net/previous?host=nathanupchurch.com",
|
||||||
|
nextURL: "https://fediring.net/next?host=nathanupchurch.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Geekring",
|
||||||
|
ringURL: "https://geekring.net/",
|
||||||
|
previousURL: "http://geekring.net/site/350/previous",
|
||||||
|
nextURL: "http://geekring.net/site/350/next"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
13
_includes/footer.njk
Normal file
13
_includes/footer.njk
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<footer>
|
||||||
|
<p>{% if metadata.copyrightNotice %}<span class="copyright-notice">{{ metadata.copyrightNotice }}</span>{% endif %}
|
||||||
|
|
||||||
|
{% if metadata.webrings %}<br>
|
||||||
|
{% for webring in metadata.webrings %}
|
||||||
|
<span class="webring">
|
||||||
|
{% if webring.previousURL %}<a href="{{ webring.previousURL }}">←</a>{% endif %}
|
||||||
|
{% if webring.ringURL %}<a href="{{ webring.ringURL }}">{{ webring.name }}</a>{% endif %}
|
||||||
|
{% if webring.nextURL %}<a href="{{ webring.nextURL }}">→</a>{% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</p>
|
||||||
|
</footer>
|
12
_includes/header.njk
Normal file
12
_includes/header.njk
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<a href="#skip" class="visually-hidden">Skip to main content</a>
|
||||||
|
<header>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="home-link">
|
||||||
|
<img
|
||||||
|
class="logo"
|
||||||
|
src="{{ metadata.logo }}"
|
||||||
|
alt="{{ metadata.title }}">
|
||||||
|
</a>
|
||||||
|
{% if not hideNav %}{% include "nav.njk" %}{% endif %}
|
||||||
|
</header>
|
7
_includes/layouts/403.njk
Normal file
7
_includes/layouts/403.njk
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
---
|
||||||
|
<h1>403 Forbidden</h1>
|
||||||
|
<p class="nodropcap page-block">*Ahem* <a href="/">Go to the home page</a>.</p>
|
||||||
|
|
||||||
|
{{ content | safe }}
|
@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: layouts/base.njk
|
layout: layouts/base.njk
|
||||||
showPostListHeader: yep
|
|
||||||
---
|
---
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<p class="nodropcap page-block">Sorry, it looks like that link is broken. <a href="/">Go to the home page</a>.</p>
|
<p class="nodropcap page-block">Sorry, it looks like that link is broken. <a href="/">Go to the home page</a>.</p>
|
||||||
|
@ -1,100 +1,21 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="{{ metadata.language }}">
|
<html lang="{{ metadata.language }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
{% include "metadata.njk" %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ title or metadata.title }}</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="/img/logo_favicon.svg">
|
|
||||||
<meta name="description" content="{{ description or metadata.description }}">
|
|
||||||
|
|
||||||
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="{{ metadata.title }}">
|
|
||||||
<link rel="alternate" href="/feed/feed.json" type="application/json" title="{{ metadata.title }}">
|
|
||||||
|
|
||||||
<meta name="generator" content="{{ eleventy.generator }}">
|
|
||||||
{#- Bundle CSS #}
|
{#- Bundle CSS #}
|
||||||
{%- css %}{% include "public/css/index.css" %}{% endcss %}
|
{%- css %}{% include "public/css/index.css" %}{% endcss %}
|
||||||
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
|
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
|
||||||
{%- css %}{% include "public/css/dropcap.css" %}{% endcss %}
|
|
||||||
<style>{% getBundle "css" %}</style>
|
<style>{% getBundle "css" %}</style>
|
||||||
{% if title %}
|
{% include "structuredData.njk" %}
|
||||||
<!-- Structured Data -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org/",
|
|
||||||
"@type": "Article",
|
|
||||||
"headline": "{{ title }}",
|
|
||||||
"author": {
|
|
||||||
"@type": "Person",
|
|
||||||
"name": "{{ metadata.author.name }}"
|
|
||||||
},
|
|
||||||
"datePublished": "{{ date }}",
|
|
||||||
"description": "{% if synopsis %}{{ synopsis}}{% endif %}",
|
|
||||||
"image": "{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}",
|
|
||||||
"url": "{{ page.url | htmlBaseUrl(metadata.url) }}",
|
|
||||||
"articleBody": "{{ content | striptags }}"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Open Graph -->
|
|
||||||
<meta property="og:type" content="article" />
|
|
||||||
<meta property="og:title" content="{{ title }}" />
|
|
||||||
<meta property="og:description" content="{{ synopsis}}" />
|
|
||||||
<meta property="og:url" content="{{ page.url | htmlBaseUrl(metadata.url) }}" />
|
|
||||||
<meta property="og:image" content="{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}" />
|
|
||||||
|
|
||||||
<!-- Twitter card -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:title" content="{{ title }}" />
|
|
||||||
<meta name="twitter:description" content="{{ synopsis}}" />
|
|
||||||
<meta name="twitter:image" content="{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}" />
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include "umami.html" %}
|
{% include "umami.html" %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a href="#skip" class="visually-hidden">Skip to main content</a>
|
{% include "header.njk" %}
|
||||||
|
|
||||||
<header>
|
|
||||||
<a href="/" class="home-link"><img src="{{ metadata.logo }}" alt="{{ metadata.title }}"></a>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<h2 class="visually-hidden">Top level navigation menu</h2>
|
|
||||||
<ul class="nav">
|
|
||||||
{%- for entry in collections.all | eleventyNavigation %}
|
|
||||||
<a class="nav-item" href="{{ entry.url }}"{% if entry.url == page.url %} aria-current="page"{% endif %}><li {% if entry.url == page.url %} data-currentpage="true"{% endif %}>{{ entry.title }}</li></a>
|
|
||||||
{%- endfor %}
|
|
||||||
<a class="nav-item" href="/feed/feed.xml">
|
|
||||||
<li subscribe">
|
|
||||||
<!-- RSS Logo -->
|
|
||||||
<svg class="nav-icon" viewBox="0 0 155 155" width="153.349" height="152.909" version="1.0" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="translate(-427.323 -373.814)">
|
|
||||||
<ellipse
|
|
||||||
style="opacity:1;fill-opacity:1;fill-rule:nonzero;"
|
|
||||||
transform="matrix(.86996 0 0 .86996 135.156 330.529)"
|
|
||||||
cx="360.357" cy="200.643" rx="24.643" ry="23.929"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
style="fill-opacity:1;fill-rule:evenodd;"
|
|
||||||
d="m427.835 455.057-.073-30.273c64.706 3.375 100.619 49.673 101.5 101.94h-30.318c-.503-45.942-31.74-69.996-71.11-71.667z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
style="fill-opacity:1;fill-rule:evenodd;"
|
|
||||||
d="m428.201 404.571-.878-30.757C526.75 378.43 580 450.582 580.67 526.724l-31.197-.44c1.365-48.704-34.665-120.267-121.273-121.713Z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>Subscribe
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="skip">
|
<main id="skip">
|
||||||
|
<section>
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
{% include "footer.njk" %}
|
||||||
<footer></footer>
|
|
||||||
|
|
||||||
<!-- Current page: {{ page.url | htmlBaseUrl }} -->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,35 +1,17 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="{{ metadata.language }}" class="barebones">
|
<html lang="{{ metadata.language }}" class="barebones">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
{% include "metadata.njk" %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ title or metadata.title }}</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="/img/logo_favicon.svg">
|
|
||||||
<meta name="description" content="{{ description or metadata.description }}">
|
|
||||||
|
|
||||||
<meta name="generator" content="{{ eleventy.generator }}">
|
|
||||||
|
|
||||||
{#- Bundle CSS #}
|
{#- Bundle CSS #}
|
||||||
{%- css %}{% include "public/css/index.css" %}{% endcss %}
|
{%- css %}{% include "public/css/index.css" %}{% endcss %}
|
||||||
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
|
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
|
||||||
{%- css %}{% include "public/css/dropcap.css" %}{% endcss %}
|
|
||||||
<style>{% getBundle "css" %}</style>
|
<style>{% getBundle "css" %}</style>
|
||||||
|
|
||||||
{% include "umami.html" %}
|
{% include "umami.html" %}
|
||||||
</head>
|
</head>
|
||||||
<body class="barebones">
|
<body class="barebones">
|
||||||
<a href="#skip" class="visually-hidden">Skip to main content</a>
|
{% include "header.njk" %}
|
||||||
|
|
||||||
<header>
|
|
||||||
<a href="/" class="home-link"><img src="{{ metadata.logo }}" alt="{{ metadata.title }}"></a>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="skip">
|
<main id="skip">
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer></footer>
|
|
||||||
|
|
||||||
<!-- Current page: {{ page.url | htmlBaseUrl }} -->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
layout: layouts/base.njk
|
layout: layouts/base.njk
|
||||||
showPostListHeader: yep
|
showPostListHeader: yep
|
||||||
---
|
---
|
||||||
<h1>The personal website and blog of Nathan Upchurch.</h1>
|
<h1>Hi there, friend.</h1>
|
||||||
<p class="nodropcap page-block">Welcome to my personal website and blog, where I write about tech, free and open source software, design, vegan cooking, incense, music, and all sorts of <a href="/tags">other topics</a> that I find interesting.</p>
|
<p class="nodropcap page-block">My name is Nathan Upchurch. Welcome to my personal website and blog, where I write about <a href="./tags/incense/">incense,</a> <a href="./tags/foss-floss/">free and open source software,</a> design, <a href="./tags/vegan-cooking/">vegan cooking,</a> music, and all sorts of <a href="/tags">other topics</a> that I find interesting. Learn more <a href="about">about me,</a> see <a href="now">what I’ve been up to lately,</a> or have a look at my latest blog posts below.</p>
|
||||||
|
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
---
|
---
|
||||||
layout: layouts/baseBareBones.njk
|
layout: layouts/baseBareBones.njk
|
||||||
|
hideNav: please
|
||||||
---
|
---
|
||||||
{%- css %}{% include "public/css/links.css" %}{% endcss %}
|
{%- css %}{% include "public/css/me.css" %}{% endcss %}
|
||||||
|
<div class="links-container">
|
||||||
<img class="profilePic" src="{{ metadata.author.profilePic }}">
|
<img class="profilePic" src="{{ metadata.author.profilePic }}">
|
||||||
<h1 class="socialTitle">Nathan Upchurch</h1>
|
<h1 class="socialTitle">Nathan Upchurch</h1>
|
||||||
<p class="page-block nodropcap">Here's where you can find me on the internet:</p>
|
<p class="page-block nodropcap">Here's where you can find me on the internet:</p>
|
||||||
|
<div class="socialLinks">
|
||||||
<ul class="socialLinks">
|
|
||||||
{% for link in metadata.socialLinks %}
|
{% for link in metadata.socialLinks %}
|
||||||
<a {% if link.customAttribute %} {{ link.customAttribute | safe }} {% endif %} href="{{ link.linkURL }}"><li><img src="{{ link.iconURL }}" />{{ link.linkDisplay }}</li></a>
|
<a class="link-button" {% if link.customAttribute %} {{ link.customAttribute | safe }} {% endif %} href="{{ link.linkURL }}">
|
||||||
|
<button type="button"><img src="{{ link.iconURL }}" />{{ link.linkDisplay }}</button>
|
||||||
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -3,26 +3,45 @@ layout: layouts/base.njk
|
|||||||
---
|
---
|
||||||
{# Only include the syntax highlighter CSS on blog posts #}
|
{# Only include the syntax highlighter CSS on blog posts #}
|
||||||
{%- css %}{% include "public/css/code.css" %}{% endcss %}
|
{%- css %}{% include "public/css/code.css" %}{% endcss %}
|
||||||
|
<article class="post">
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
|
{% if not hideMetadata %}
|
||||||
|
<div class="post-metadata">
|
||||||
|
{% if author %}
|
||||||
|
{% if author.profilePic %}
|
||||||
|
<img class="profilePic" src="{{ author.profilePic }}">
|
||||||
|
{% endif %}
|
||||||
|
<div class="post-metadata-copy">
|
||||||
|
<p>{% if author.url %}<a href="{{ author.url }}">{% endif %}
|
||||||
|
{% if author.name %}{{ author.name }}, {% endif %}{% if author.url %}</a>{% endif %}<time datetime="{{ page.date | htmlDateString }}">{{ page.date | readableDate }}</time></p>
|
||||||
|
|
||||||
<ul class="post-metadata">
|
{% else %}
|
||||||
<li><time datetime="{{ page.date | htmlDateString }}">{{ page.date | readableDate }}</time></li>
|
{% if metadata.author.profilePic %}
|
||||||
|
<img class="profilePic" src="{{ metadata.author.profilePic }}">
|
||||||
|
{% endif %}
|
||||||
|
<div class="post-metadata-copy">
|
||||||
|
<p>{% if metadata.author.url %}<a href="{{ metadata.author.url }}">{% endif %}
|
||||||
|
{% if metadata.author.name %}{{ metadata.author.name }}, {% endif %}{% if metadata.author.url %}</a>{% endif %}<time datetime="{{ page.date | htmlDateString }}">{{ page.date | readableDate }}</time></p>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
{%- for tag in tags | filterTagList %}
|
{%- for tag in tags | filterTagList %}
|
||||||
{%- set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
|
{%- set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
|
||||||
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a></li>
|
<li>
|
||||||
|
<a
|
||||||
|
href="{{ tagUrl }}"
|
||||||
|
class="post-tag">
|
||||||
|
{{ tag }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
|
</article>
|
||||||
{%- if collections.posts %}
|
{% include "mastodonComments.njk" %}
|
||||||
{%- set previousPost = collections.posts | getPreviousCollectionItem %}
|
{% include "nextLast.njk" %}
|
||||||
{%- set nextPost = collections.posts | getNextCollectionItem %}
|
|
||||||
{%- if nextPost or previousPost %}
|
|
||||||
<ul class="links-nextprev">
|
|
||||||
{%- if previousPost %}<li>Previous: <a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a></li>{% endif %}
|
|
||||||
{%- if nextPost %}<li>Next: <a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a></li>{% endif %}
|
|
||||||
</ul>
|
|
||||||
<p>Questions? Comments? <a href="../../me">contact me</a>.</p>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endif %}
|
|
||||||
|
81
_includes/mastodonComments.njk
Normal file
81
_includes/mastodonComments.njk
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{% if mastodon_id %}
|
||||||
|
<section class="" id="comment-section">
|
||||||
|
<h2>Comments</h2>
|
||||||
|
<div class="comment-ingress"></div>
|
||||||
|
<div id="comments" data-id="{{ mastodon_id }}">
|
||||||
|
<p>Loading comments...</p>
|
||||||
|
</div>
|
||||||
|
<div class="continue-discussion">
|
||||||
|
<a class="link-button" href="https://{{ metadata.mastodonHost }}/@{{ metadata.mastodonUser }}/{{ mastodon_id }}">
|
||||||
|
<button type="button">
|
||||||
|
Reply on Mastodon to comment »
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<template id="comment-template">
|
||||||
|
<wc-card>
|
||||||
|
<wc-comment
|
||||||
|
author_name=""
|
||||||
|
author_url=""
|
||||||
|
avatar_url=""
|
||||||
|
comment_content=""
|
||||||
|
publish_date=""
|
||||||
|
sharp_corner="">
|
||||||
|
</wc-comment>
|
||||||
|
</wc-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import {dateSuffixAdder, monthMap, timeFormatter} from "../../js/modules/mastodonDateTools.js";
|
||||||
|
|
||||||
|
const renderComment = (comment, target, parentIdm) => {
|
||||||
|
const node = document
|
||||||
|
.querySelector("template#comment-template")
|
||||||
|
.content.cloneNode(true);
|
||||||
|
|
||||||
|
const dateObj = new Date(comment.created_at);
|
||||||
|
|
||||||
|
const dateTime = `${dateObj.getDate()}${dateSuffixAdder(dateObj.getDate())} of ${monthMap[dateObj.getMonth()]}, ${dateObj.getFullYear()}, at ${timeFormatter(dateObj.getHours(), dateObj.getMinutes())}`;
|
||||||
|
|
||||||
|
node.querySelector("wc-comment").setAttribute("author_name", comment.account.display_name);
|
||||||
|
node.querySelector("wc-comment").setAttribute("author_url", comment.url.replace(/\/[0-9]+/, ""));
|
||||||
|
node.querySelector("wc-comment").setAttribute("avatar_url", comment.account.avatar_static);
|
||||||
|
node.querySelector("wc-comment").setAttribute("comment_content", comment.content);
|
||||||
|
node.querySelector("wc-comment").setAttribute("publish_date", dateTime);
|
||||||
|
|
||||||
|
target.appendChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderComments() {
|
||||||
|
const commentsNode = document.querySelector("#comments");
|
||||||
|
|
||||||
|
const mastodonPostId = commentsNode.dataset?.id;
|
||||||
|
|
||||||
|
if (!mastodonPostId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsNode.innerHTML = "";
|
||||||
|
|
||||||
|
const originalPost = await fetch(
|
||||||
|
`https://{{ metadata.mastodonHost }}/api/v1/statuses/${mastodonPostId}`
|
||||||
|
);
|
||||||
|
const originalData = await originalPost.json();
|
||||||
|
renderComment(originalData, commentsNode, null);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://{{ metadata.mastodonHost }}/api/v1/statuses/${mastodonPostId}/context`
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
const comments = data.descendants;
|
||||||
|
|
||||||
|
comments.forEach((comment) => {
|
||||||
|
renderComment(comment, commentsNode, mastodonPostId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderComments();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
10
_includes/metadata.njk
Normal file
10
_includes/metadata.njk
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ title or metadata.title }}</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/logo_favicon.svg">
|
||||||
|
<meta name="description" content="{{ description or metadata.description }}">
|
||||||
|
<meta name="robots" content="noai, noimageai">
|
||||||
|
<meta name="generator" content="{{ eleventy.generator }}">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="{{ metadata.title }}">
|
||||||
|
<link rel="alternate" href="/feed/feed.json" type="application/json" title="{{ metadata.title }}">
|
||||||
|
<script type="module" src="/js/main.js"></script>
|
15
_includes/nav.njk
Normal file
15
_includes/nav.njk
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
{%- for entry in collections.all | eleventyNavigation %}
|
||||||
|
<a class="nav-item" href="{{ entry.url }}"{% if entry.url == page.url %} aria-current="page" data-currentpage="true"{% endif %}>
|
||||||
|
<li>{{ entry.title }}</li>
|
||||||
|
</a>
|
||||||
|
{%- endfor %}
|
||||||
|
<a class="nav-item" href="/feed/feed.xml">
|
||||||
|
<li class="subscribe">
|
||||||
|
{% include "rssLogo.njk" %}Feed
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
62
_includes/nextLast.njk
Normal file
62
_includes/nextLast.njk
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{% if collections.posts %}
|
||||||
|
{% set previousPost = collections.posts | getPreviousCollectionItem %}
|
||||||
|
{% set nextPost = collections.posts | getNextCollectionItem %}
|
||||||
|
{% if nextPost or previousPost %}
|
||||||
|
<section class="links-nextprev">
|
||||||
|
<h2>Read Next</h2>
|
||||||
|
<div class="postlist-item-container">
|
||||||
|
{% if previousPost %}
|
||||||
|
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
|
||||||
|
<a href="{{ previousPost.url }}" class="postlist-link">
|
||||||
|
<div class="post-image-container">
|
||||||
|
<img class="post-image" {% if previousPost.data.imageURL %} src="{{ previousPost.data.imageURL }}" alt="{{ previousPost.data.imageAlt }}" {% else %} src="{{ metadata.defaultPostImageURL }}" alt="{{ metadata.defaultPostImageAlt }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="post-copy">
|
||||||
|
<a href="{{ previousPost.url }}" class="postlist-link">
|
||||||
|
<p>Previous Article:</p>
|
||||||
|
<h3>
|
||||||
|
{% if previousPost.data.title %}{{ previousPost.data.title }}{% else %}<code>{{ previousPost.url }}</code>{% endif %}
|
||||||
|
</h3>
|
||||||
|
</a>
|
||||||
|
<time class="postlist-date" datetime="{{ previousPost.date | htmlDateString }}">{{ previousPost.date | readableDate("LLLL yyyy") }}</time>
|
||||||
|
{% if previousPost.data.synopsis %}
|
||||||
|
<p>{{ previousPost.data.synopsis | truncate(105) | safe }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{{ previousPost.content | truncate(105) | safe }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% if not nextPost %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if nextPost %}
|
||||||
|
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
|
||||||
|
<a href="{{ nextPost.url }}" class="postlist-link">
|
||||||
|
<div class="post-image-container">
|
||||||
|
<img class="post-image" {% if nextPost.data.imageURL %} src="{{ nextPost.data.imageURL }}" alt="{{ nextPost.data.imageAlt }}" {% else %} src="{{ metadata.defaultPostImageURL }}" alt="{{ metadata.defaultPostImageAlt }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="post-copy">
|
||||||
|
<a href="{{ nextPost.url }}" class="postlist-link">
|
||||||
|
<p>Next Article:</p>
|
||||||
|
<h3>
|
||||||
|
{% if nextPost.data.title %}{{ nextPost.data.title }}{% else %}<code>{{ nextPost.url }}</code>{% endif %}
|
||||||
|
</h3>
|
||||||
|
</a>
|
||||||
|
<time class="postlist-date" datetime="{{ nextPost.date | htmlDateString }}">{{ nextPost.date | readableDate("LLLL yyyy") }}</time>
|
||||||
|
{% if nextPost.data.synopsis %}
|
||||||
|
<p>{{ nextPost.data.synopsis | truncate(105) | safe }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{{ nextPost.content | truncate(105) | safe }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
@ -1,5 +1,5 @@
|
|||||||
<section class="postlist">
|
<section class="postlist">
|
||||||
{% if showPostListHeader %}<h2>Latest Posts</h2>{% endif %}
|
{% if showPostListHeader %}<h2>{{ metadata.postlistHeaderText }}</h2>{% endif %}
|
||||||
<div class="postlist-item-container">
|
<div class="postlist-item-container">
|
||||||
{% for post in postslist | reverse %}
|
{% for post in postslist | reverse %}
|
||||||
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
|
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
|
||||||
@ -15,7 +15,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</a>
|
</a>
|
||||||
<time class="postlist-date" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate("LLLL yyyy") }}</time>
|
<time class="postlist-date" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate("LLLL yyyy") }}</time>
|
||||||
{% if post.data.synopsis %}<p>{{ post.data.synopsis | truncate(110) | safe }}</p>{% else %}{{ post.content | truncate(140) | safe }}{% endif %}
|
{% if post.data.synopsis %}<p>{{ post.data.synopsis | truncate(105) | safe }}</p>{% else %}{{ post.content | truncate(105) | safe }}{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
19
_includes/rssLogo.njk
Normal file
19
_includes/rssLogo.njk
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!-- RSS Logo -->
|
||||||
|
<svg class="nav-icon" viewBox="0 0 155 155" width="153.349" height="152.909" version="1.0" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-427.323 -373.814)">
|
||||||
|
<ellipse
|
||||||
|
style="opacity:1;fill-opacity:1;fill-rule:nonzero;"
|
||||||
|
transform="matrix(.86996 0 0 .86996 135.156 330.529)"
|
||||||
|
cx="360.357" cy="200.643" rx="24.643" ry="23.929"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill-opacity:1;fill-rule:evenodd;"
|
||||||
|
d="m427.835 455.057-.073-30.273c64.706 3.375 100.619 49.673 101.5 101.94h-30.318c-.503-45.942-31.74-69.996-71.11-71.667z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill-opacity:1;fill-rule:evenodd;"
|
||||||
|
d="m428.201 404.571-.878-30.757C526.75 378.43 580 450.582 580.67 526.724l-31.197-.44c1.365-48.704-34.665-120.267-121.273-121.713Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<!-- /RSS Logo -->
|
After Width: | Height: | Size: 779 B |
30
_includes/structuredData.njk
Normal file
30
_includes/structuredData.njk
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{% if title %}
|
||||||
|
<!-- Structured Data -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org/",
|
||||||
|
"@type": "Article",
|
||||||
|
"headline": "{{ title }}",
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "{{ metadata.author.name }}"
|
||||||
|
},
|
||||||
|
"datePublished": "{{ date }}",
|
||||||
|
"description": "{% if synopsis %}{{ synopsis}}{% endif %}",
|
||||||
|
"image": "{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}",
|
||||||
|
"url": "{{ page.url | htmlBaseUrl(metadata.url) }}",
|
||||||
|
"articleBody": "{{ content | striptags }}"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- Open Graph -->
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content="{{ title }}" />
|
||||||
|
<meta property="og:description" content="{{ synopsis}}" />
|
||||||
|
<meta property="og:url" content="{{ page.url | htmlBaseUrl(metadata.url) }}" />
|
||||||
|
<meta property="og:image" content="{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}" />
|
||||||
|
<!-- Twitter card -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="{{ title }}" />
|
||||||
|
<meta name="twitter:description" content="{{ synopsis}}" />
|
||||||
|
<meta name="twitter:image" content="{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}" />
|
||||||
|
{% endif %}
|
5
content/403.md
Normal file
5
content/403.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/403.njk
|
||||||
|
permalink: 403.html
|
||||||
|
eleventyExcludeFromCollections: true
|
||||||
|
---
|
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
layout: layouts/home.njk
|
|
||||||
permalink: 404.html
|
|
||||||
eleventyExcludeFromCollections: true
|
|
||||||
---
|
|
||||||
# Content not found.
|
|
||||||
|
|
||||||
Go <a href="/">home</a>.
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
Read more: https://www.11ty.dev/docs/quicktips/not-found/
|
|
||||||
|
|
||||||
This will work for both GitHub pages and Netlify:
|
|
||||||
|
|
||||||
* https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/
|
|
||||||
* https://www.netlify.com/docs/redirects/#custom-404
|
|
||||||
|
|
||||||
-->
|
|
69
content/about-feeds/index.md
Normal file
69
content/about-feeds/index.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/post.njk
|
||||||
|
hideMetadata: yep
|
||||||
|
---
|
||||||
|
# How to use feeds.
|
||||||
|
<p><!-- a <p> just to stop the dropcap from happening --></p>
|
||||||
|
|
||||||
|
Get all the latest content from your favorite creators with no algorithm, no spam, and no spying. This page is based on [Matt Webb](https://interconnected.org)'s *[About Feeds](https://aboutfeeds.com/)*.
|
||||||
|
|
||||||
|
[](../img/akregator.png)
|
||||||
|
|
||||||
|
## What is a feed?
|
||||||
|
|
||||||
|
* A "web feed," or an "RSS feed," is a stream of all latest content from a blog or a website. You can subscribe to these feeds using a newsreader app.
|
||||||
|
|
||||||
|
* Many websites such as news sites and blogs already have feeds. You'll often see a link at the top or bottom of the page that says "RSS," "Feed," or an icon like this: <img src="../img/RSS-Orange.svg" style="height: .75rem;">.
|
||||||
|
|
||||||
|
* Whenever you see a website with a feed, that means you can subscribe to that site.
|
||||||
|
|
||||||
|
## Why use feeds?
|
||||||
|
|
||||||
|
* Subscribing to feeds is a quick way to access news and content from many different sources in one place: your newsreader.
|
||||||
|
|
||||||
|
* With feeds, there's no spam, no social media drama, and no algorithm choosing what you see — just a curated collection of all the content you want to see, for free.
|
||||||
|
|
||||||
|
## How do I get a newsreader app?
|
||||||
|
|
||||||
|
**To subscribe to feeds, you'll need a newsreader app.** Once you've subscribed to a feed, your newsreader will check it regularly and show you the latest in an inbox, a bit like email.
|
||||||
|
|
||||||
|
There are many different newsreader apps to choose from. Below are a few you could try; find more by searching "RSS Feed Reader" on the internet, or in the app store on your device.[^1]
|
||||||
|
|
||||||
|
* Feeder (Android — [Google Play](https://play.google.com/store/apps/details?id=com.nononsenseapps.feeder.play), [F-Droid](https://f-droid.org/packages/com.nononsenseapps.feeder/))
|
||||||
|
|
||||||
|
* [Fluent Reader (Windows, macOS)](https://hyliu.me/fluent-reader/)
|
||||||
|
|
||||||
|
* [Raven (GNU/Linux, Windows, macOS)](https://ravenreader.app/)
|
||||||
|
|
||||||
|
* [NetNewsWire (macOS, iOS)](https://ravenreader.app/)
|
||||||
|
|
||||||
|
* [FreshRSS (Online)](https://www.freshrss.org/)
|
||||||
|
|
||||||
|
* [Akregator (GNU/Linux)](https://apps.kde.org/akregator/)
|
||||||
|
|
||||||
|
* [RSSOwl (GNU/Linux, Windows, macOS)](https://www.rssowl.org/)
|
||||||
|
|
||||||
|
* [RSS Guard (GNU/Linux, Windows, macOS, BSD, OS/2)](https://github.com/martinrotter/rssguard)
|
||||||
|
|
||||||
|
|
||||||
|
It doesn't matter which you choose; newsreaders usually make it fairly easy to export your list of subscriptions and move them to another app in the future.
|
||||||
|
|
||||||
|
## How do I use my new newsreader app to subscribe to a feed?
|
||||||
|
|
||||||
|
[](../img/akregator_add_feed.png)
|
||||||
|
|
||||||
|
1. Get the Feed URL. To get this, go to the website you want to subscribe to and find that RSS link or the feed icon. Then...
|
||||||
|
* On **desktop**, right click on the link and choose "Copy Link Address" or similar.
|
||||||
|
* On **mobile**, tap and hold the link until a menu comes up. Choose "Copy Link" or similar.
|
||||||
|
|
||||||
|
2. Add the URL to your newsreader; go to your newsreader app and look for something like "Subscribe," "Add Web Feed," or maybe you'll just see a "+" icon. Once you've found this, paste in the feed URL and you're done!
|
||||||
|
|
||||||
|
|
||||||
|
## What next?
|
||||||
|
|
||||||
|
* Find more to read: Browse [Blogroll](https://blogroll.org) and [ooh.directory](https://ooh.directory), both hand-curated directories of blogs, organised by topic.
|
||||||
|
|
||||||
|
* Why not start a blog of your own? [Get Blogging!](https://getblogging.org) is a guide to the practicalities of starting a blog.
|
||||||
|
|
||||||
|
|
||||||
|
[^1]: Please note that I haven't tested all of these options personally; your mileage may vary.
|
@ -4,5 +4,19 @@ eleventyNavigation:
|
|||||||
key: About
|
key: About
|
||||||
order: 3
|
order: 3
|
||||||
---
|
---
|
||||||
# About the author, Nathan Upchurch.
|
<article>
|
||||||
<p class="page-block nodropcap">I'm a prolific vegan home cook, classical trombonist, a <abbr title="Free/Libre Open Source Software">FLOSS</abbr> enthusiast, daily GNU/Linux user and unabashed <a href="https://kde.org/">KDE</a> stan, speaker of subpar elementary Spanish, incense enthusiast, writer, electronics hobbyist, designer, programmer, music producer, print lover, and human with too many interests and too little time. This is my personal website and blog. <br><br>Because this is my <em>personal</em> website, I'm not here to talk about work, but I will link my professional bio here when I've gotten around to making it. If you would like to say something nice, you can find me on <a href="https://mastodon.social/@nathanu">Mastodon</a>.<br><br>This website is made with <a href="https://www.11ty.dev/">11ty</a>, <a href="https://www.gent.media/manrope">Manrope</a>, <a href="https://github.com/clauseggers/Playfair">Playfair Display</a>, and plain-old HTML & CSS. I don't collect any of your data, full-stop. All webfonts, icons, and images are hosted locally (Instead of by Google, for instance, or any other company which might<a href="https://www.firstpost.com/world/how-google-uses-fonts-to-track-what-users-do-online-and-sell-data-to-advertisers-12496552.html"> use them to track you</a>).<br><hr>Find more blogs at <a href="https://blogroll.org">blogroll.org</a>, or <a href="https://ooh.directory/">ooh.directory</a>. | <a href="https://keyoxide.org/31E809FAEA1532AC91BBDCF1EC499D3513F69340">Keyoxide profile</a></p>
|
<h1>About me and this website I’ve built.</h1>
|
||||||
|
<p>I’m a prolific vegan home cook, classical trombonist, a <abbr title="Free/Libre Open Source Software">FLOSS</abbr> enthusiast, daily GNU/Linux user and unabashed <a href="https://kde.org/">KDE</a> stan, speaker of subpar elementary Spanish, incense maker and appreciator, writer, electronics hobbyist, designer, programmer, music producer, print lover, and human with too many interests and too little time.</p>
|
||||||
|
|
||||||
|
<p>This is my personal website and blog, a little corner of the internet where I can talk about whatever I like without worrying about maintaining a ‘personal brand’, or constraining subject matter to those topics which might help advance my career or establish me as a ‘thought leader’. I’m here to express myself as a human and have fun writing about topics I enjoy. If you’d like to learn more about my professional accomplishments and work, I’ll link my professional website here soon.</p>
|
||||||
|
|
||||||
|
<h2>About this website</h2>
|
||||||
|
<p>This website is made with <a href="https://www.11ty.dev/">the 11ty static site generator</a>, free and open source variable typefaces <a href="https://fraunces.undercase.xyz/">Fraunces</a> and <a href="https://www.gent.media/manrope">Manrope</a>, along with plain-old HTML, CSS, and some vanilla JavaScript for the web components that I built to handle comments. I used the handy calculators on <a href="https://utopia.fyi">utopia.fyi</a> to help me implement fluid typography and spacing.</p>
|
||||||
|
|
||||||
|
<p>I don’t collect any of your personal information, full-stop. All webfonts, icons, and images are hosted locally (these things can sometimes be used to <a href="https://www.firstpost.com/world/how-google-uses-fonts-to-track-what-users-do-online-and-sell-data-to-advertisers-12496552.html">track people across the internet</a> otherwise). I use <a href="https://umami.is/">umami</a>, an open source, privacy-respecting analytics tool, to see how many people visit this website.</p>
|
||||||
|
|
||||||
|
<p>Miss when the internet was fun? Find more interesting personal blogs at <a href="https://blogroll.org">blogroll.org</a>, <a href="https://ooh.directory/">ooh.directory</a>, or surf through the webrings at the bottom of the page.</p>
|
||||||
|
|
||||||
|
<h2>Contact Me</h2>
|
||||||
|
<p>If you would like to say something nice, ask a question, or simply follow me on the fediverse, <a href="../me">here’s where you can find me</a>.</p>
|
||||||
|
</article>
|
||||||
|
@ -4,11 +4,11 @@ eleventyNavigation:
|
|||||||
key: Blog
|
key: Blog
|
||||||
order: 2
|
order: 2
|
||||||
---
|
---
|
||||||
<h1>Nathan Upchurch’s Personal Blog: Latest Posts.</h1>
|
<h1>Nathan’s Blog.</h1>
|
||||||
|
|
||||||
<p class="page-block nodropcap">
|
<p class="page-block nodropcap">
|
||||||
Looking for something in particular? Have a look at <a href="/tags/">this convenient list of post categories</a> to filter results by topic.
|
Looking for something in particular? Have a look at <a href="/tags/">this convenient list of post categories</a> to filter results by topic.
|
||||||
</p>
|
</p>
|
||||||
|
<h2>What’s New:</h2>
|
||||||
{% set postslist = collections.posts %}
|
{% set postslist = collections.posts %}
|
||||||
{% include "postslist.njk" %}
|
{% include "postslist.njk" %}
|
||||||
|
57
content/blog/a-breakthrough-incense-blend.md
Normal file
57
content/blog/a-breakthrough-incense-blend.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: "Grand Dame: A Breakthrough Incense Blend?"
|
||||||
|
description: "An attempt at a lavender incense stick goes remarkably well thanks to an unusual technique."
|
||||||
|
date: 2024-07-28
|
||||||
|
tags:
|
||||||
|
- Incense
|
||||||
|
synopsis: "An attempt at a lavender incense stick goes remarkably well thanks to an unusual technique."
|
||||||
|
imageURL: /img/sending-incense-samples.webp
|
||||||
|
imageAlt: An uncapped fountain pen on top of a pretty, gold-foiled pad of paper beside some envelopes with stamps featuring coffee drinks on them.
|
||||||
|
mastodon_id: "112867886475498806"
|
||||||
|
---
|
||||||
|
I wrote two letters today, sealing each into a cotton envelope alongside a colorful cardboard straw, taped at both ends, containing two minuscule sticks of incense. I'm very excited about what's in those straws: incense sticks resulting from a blend I've dubbed *Grand Dame.*
|
||||||
|
|
||||||
|
---
|
||||||
|
*A quick note:
|
||||||
|
You can [see everything I've written on incense here,](../../tags/incense/) or [subscribe to just incense posts via RSS.](../../feeds/incense.xml)*
|
||||||
|
|
||||||
|
*Correction: When this article was first published, I wrongly attributed the suggestion that Yi-Xin's "Heart of Lavender" isn't likely to use lavender essential oil to Irene of [rauchfahne.de](https://blog.rauchfahne.de/en/) who has since let me know that it wasn't her who suggested this.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](/img/sending-incense-samples.webp)
|
||||||
|
|
||||||
|
These sticks are interesting for a couple of reasons. First, they make use of ambrette seeds, which are mentioned in places such as [incensemaking.com,](https://incensemaking.com/aromatics/musk-seeds/) but there's precious little information available about people *actually using them* in incense. The sticks also involve an experimental technique that I used to try to achieve a lavender fragrance similar to that in Yi-Xin's *Heart of Lavender.*
|
||||||
|
|
||||||
|
## Ambrette
|
||||||
|
Ambrette seeds, or musk seeds, are used in perfumery as a natural alternative to animal musk. Far be it from me to deprive an innocent creature of any of its organs, no matter how fragrant, I have been curious about this ingredient for some time as a way to imbue incense with a measure of animalic depth and complexity.
|
||||||
|
|
||||||
|
One of the factors that makes traditional incense making so difficult is that most plants reek to high heaven when burned unless you very precisely control the ratio of the ingredient and the temperature at which it burns. Knowing that tonka beans are notorious for smelling less than rosy when used in too high a concentration, I treated the ambrette seeds with a similar trepidation. Through experimentation, I've found that ambrette seeds make their presence well known at as little as two percent of a total blend.
|
||||||
|
|
||||||
|
Ambrette seed really does need to be a part of a blend. Upon lighting, it's one of the first ingredients you'll notice, and as a stick of *Grand Dame* was burning while I wore a mask during a visit to Dave of [The World Makes Scents](https://theworldmakesscents.com/) in his workshop (a great time that I plan to write about soon - thanks Dave!), the ambrette was one of the few notes that made it through the tight fibers of a KN95. In isolation, the burning musk seeds aren't pretty, but I really think they add something special to the blend as a whole.
|
||||||
|
|
||||||
|
## The Experiment
|
||||||
|
I've been a bit obsessed with Yi-Xin's [*Heart of Lavender*](https://craft-incense.com/products/lavenwood) since I first tried it. You see, creating floral incense is notoriously difficult; burning flowers rarely produce a fragrance that's even remotely pleasant, and when they do, it's still tricky to get the blend right. As Ken of Yi-Xin wrote on the *Heart of Lavender* product page:
|
||||||
|
|
||||||
|
>Blending lavender flowers into incense basically makes the scent quite herbaceous and sharp. So it took a lot of tweaking and some special techniques to get correct.
|
||||||
|
|
||||||
|
He also hints at one of these special techniques:
|
||||||
|
|
||||||
|
>Firstly, the base ingredient is a specially processed Stanford Cedar material that integrated lavender in a very unique way
|
||||||
|
|
||||||
|
Whatever Ken is doing to those ingredients, it produces a beautiful lavender stick which somehow circumvents the sharpness he describes altogether. It's practically *juicy* — floral, but fruity and tart like a plum. As an incense maker, how you could know that this is possible and *not* try to do it is beyond me, so I came up with an idea and tested my hypothesis in *Grand Dame.* The best guess I could muster as to how he'd managed to avoid that familiar scent of burning plant material that is usually part and parcel of, well, burning plant material, was that he must have either omitted it entirely or significantly reduced the quantity used. If I recall, someone mentioned to me at one point that *Heart of Lavender* didn't strike them as containing any significant quantity of essential oil, and I had forgotten that hydrosols exist, which left me with the idea that perhaps Ken had used a tincture.
|
||||||
|
|
||||||
|
I started out by soaking whole dried lavender flowers in [Pisco,](https://catanpisco.com/) which smelled incredible. I soon did a little reading on tinctures, however, and found that I was going about it all wrong. The proof of the Pisco and the ratio of flowers to Pisco were both too low, and I'd need to wait several weeks to see how my tincture had turned out in any case. At this point, I bought an ounce of lavender tincture to experiment with. I combined the entire bottle with six grams of stunning powdered [Juniperus Virginiana from The World Makes Scents;](https://theworldmakesscents.com/products/premium-super-fine-red-cedar-powder-juniperus-virginiana) after stirring well and letting the mixture sit for twenty-four hours, I removed the lid from the jar and allowed the liquid to evaporate. The result of a quick burn test was thrilling: the trail of cedar produced the beautiful, fruity lavender fragrance that I'd hoped for.
|
||||||
|
|
||||||
|
Now that I had some lavender fragranced wood, I decided to make some sticks from it. To the cedar, I added benzoin for sweetness and for its fixative properties, acacia gum to help modulate burn temperature and to strengthen the sticks, cinnamon, borneol, a small amount of powdered lavender, the ambrette seed, and guar gum to bind. In my opinion, the sticks turned out very well.
|
||||||
|
|
||||||
|
Weeks later, I reflected on the original purpose of my tincture experiment while taking a look at the last of my *Heart of Lavender* sticks from Yi-Xin. As I did, I realised something: these sticks are *dark.* There had to be a significant amount of lavender flowers in them.
|
||||||
|
|
||||||
|
[](/img/heart-of-lavender.webp)
|
||||||
|
|
||||||
|
Here is a comparison between *Grand Dame* and *Heart of Lavender*:
|
||||||
|
|
||||||
|
|
||||||
|
[](/img/incense-stick-comparison.webp)
|
||||||
|
|
||||||
|
With that, while it produces a lovely stick, I think my tincture idea is out of the window. Knowing that Ken was taught by [Kyarazen,](https://www.kyarazen.com/) who writes on traditional Chinese incense techniques as does his student [Dr. Incense,](https://dr-incense.com/) I wonder whether the cedar and lavender might have been processed by [steaming](https://dr-incense.com/blogs/dr-incense-blog/traditional-processing-of-aloeswood) them together. At any rate, despite my disappointment at failing to discover how Ken of Yi-Xin made his lavender incense so good, I did manage to find a way of achieving a similar result, and I'm keen to hear what some of my fellow incense-heads think providing those samples arrive in good condition. It continues to be an interesting line of enquiry as tinctures are expensive, even when you make them yourself, so I'd be grateful to discover a more frugal alternative. I've also started some more tinctures to play with, one with osmanthus and another using jasmine. I'll let you know how those work out.
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
tags: [
|
tags: [
|
||||||
"posts"
|
"posts"
|
||||||
],
|
],
|
||||||
|
@ -8,8 +8,8 @@ tags:
|
|||||||
- Code Tutorial
|
- Code Tutorial
|
||||||
- SVG
|
- SVG
|
||||||
synopsis: In this tutorial, we'll learn how to make a configurable SVG graphic of a grid of circles in random colors and sizes with p5.js.
|
synopsis: In this tutorial, we'll learn how to make a configurable SVG graphic of a grid of circles in random colors and sizes with p5.js.
|
||||||
imageURL: /img/terminal.svg
|
imageURL: /img/circle-grid-post-image.webp
|
||||||
imageAlt: A stylized illustration of a terminal prompt.
|
imageAlt: A grid of multicolored circles.
|
||||||
---
|
---
|
||||||
Processing is a fantastic language for creative programming and learning how to code, allowing programmers of all skill levels to quickly and simply create complex graphics, data visualizations, and generative art. Its Javascript implementation, p5.js, is perfect for those already familiar with Javascript, or who want to use processing to make graphics for the web without the complexity of SVG, or the insanity of using CSS for complex graphics. Today we're going to build [a simple but pretty graphic using P5](#et-voila).
|
Processing is a fantastic language for creative programming and learning how to code, allowing programmers of all skill levels to quickly and simply create complex graphics, data visualizations, and generative art. Its Javascript implementation, p5.js, is perfect for those already familiar with Javascript, or who want to use processing to make graphics for the web without the complexity of SVG, or the insanity of using CSS for complex graphics. Today we're going to build [a simple but pretty graphic using P5](#et-voila).
|
||||||
|
|
||||||
@ -76,8 +76,8 @@ function setup() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
Now if we click the play icon at the top left of the Processing IDE, a browser window should open and load a page showing our sketch so far. We ought to see a viewport entirely covered with one large black canvas.
|
Now if we click the play icon at the top left of the Processing IDE, a browser window should open and load a page showing our sketch so far. We ought to see a viewport entirely covered with one large black canvas.
|
||||||
[](../../img/circle-grid-canvas.webp)
|
|
||||||
|
|
||||||
|
[](../../img/circle-grid-canvas.webp)
|
||||||
## Random results
|
## Random results
|
||||||
To meet conditions two and three of our goal, we'll need a way to get random numbers. Let's write a quick function inside `setup()` to provide us with random integers:
|
To meet conditions two and three of our goal, we'll need a way to get random numbers. Let's write a quick function inside `setup()` to provide us with random integers:
|
||||||
``` javascript
|
``` javascript
|
||||||
@ -214,7 +214,9 @@ We can check that everything is working by calling our new function at the botto
|
|||||||
``` javascript
|
``` javascript
|
||||||
generateCircleLine(10, 10, 20, 0, 5, 10, 10, 'x', [[255,255,255]]);
|
generateCircleLine(10, 10, 20, 0, 5, 10, 10, 'x', [[255,255,255]]);
|
||||||
```
|
```
|
||||||
[](../../img/circle-grid-line.webp)
|
|
||||||
|
[](../../img/circle-grid-line.webp)
|
||||||
|
|
||||||
And look at that! We have a line!
|
And look at that! We have a line!
|
||||||
|
|
||||||
## Repeating rows
|
## Repeating rows
|
||||||
@ -287,7 +289,8 @@ generateCircleGrid(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
...to see the result:
|
...to see the result:
|
||||||
[](../../img/circle-grid-grid.webp)
|
|
||||||
|
[](../../img/circle-grid-grid.webp)
|
||||||
|
|
||||||
## SVG export
|
## SVG export
|
||||||
Looking back on our goals, we've accomplished all but one: SVG export. Happily, there's [a library](https://github.com/zenozeng/p5.js-svg) that makes this trivial. All we need to do is link the script in the `<head>` of index.html,
|
Looking back on our goals, we've accomplished all but one: SVG export. Happily, there's [a library](https://github.com/zenozeng/p5.js-svg) that makes this trivial. All we need to do is link the script in the `<head>` of index.html,
|
||||||
@ -447,5 +450,7 @@ function draw() {
|
|||||||
|
|
||||||
## Et voilà
|
## Et voilà
|
||||||
And we're done! Now you can tweak the parameters and make grids with all sorts of colors and size variations, and even export and edit them in Inkscape!
|
And we're done! Now you can tweak the parameters and make grids with all sorts of colors and size variations, and even export and edit them in Inkscape!
|
||||||
[](../../img/circle-grid-complete.webp)
|
|
||||||
|
[](../../img/circle-grid-complete.webp)
|
||||||
|
|
||||||
If you'd like to make this project even better, maybe consider implementing a GUI to adjust your grid paramaters, or adding some interactivity.
|
If you'd like to make this project even better, maybe consider implementing a GUI to adjust your grid paramaters, or adding some interactivity.
|
||||||
|
41
content/blog/cedar-frankincense-incense.md
Normal file
41
content/blog/cedar-frankincense-incense.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: "Red Cedar & Frankincense"
|
||||||
|
description: I make a simple, three-ingredient, red cedar and frankincense batch of incense sticks.
|
||||||
|
date: 2024-05-08
|
||||||
|
tags:
|
||||||
|
- Incense
|
||||||
|
synopsis: I make a simple, three-ingredient, red cedar and frankincense batch of incense sticks.
|
||||||
|
imageURL: /img/cedar_frank.webp
|
||||||
|
imageAlt: A bunch of coreless incense sticks on top of some cedar planks next to a pile of frankincense tears.
|
||||||
|
mastodon_id: "112409293978326719"
|
||||||
|
---
|
||||||
|
I've seen a recurring theme on the internet among (all three of us) incense makers: sometimes you just get tired of relegating [not-quite-combustible](../gourmand-sandalwood-incense-a-perplexing-failure/) sticks to the 'use on the incense heater' pile, or making sticks that *almost* smell nice, but don't quite. After a few knock-backs like this, going back to basics and making something simple really keeps your ego from getting too bruised. That's partially why I decided to make these red cedar and frankincense sticks. In addition, I'm a huge fan of [Yi-Xin Craft Incense's](https://craft-incense.com/) white cedar and frankincense, and I'd also read that resins can really sing when used in surprisingly low quantities, so I was keen to try this out.
|
||||||
|
|
||||||
|
[](/img/cedar_frank.webp)
|
||||||
|
|
||||||
|
If you know me in meat-space, you'll likely know that I'm an avid classical trombonist. While it is obviously true that [playing Rimsky-Korsakov's *Flight of the Bumblebee* on a tenor trombone](https://inv.tux.pizza/watch?v=dfTHNpOge0Q) isn't easy, sometimes the most difficult pieces are [those that](https://inv.tux.pizza/watch?v=xKCcFxBP2o0) look [quite simple](https://inv.tux.pizza/watch?v=tcbFpgkHNc0) on paper. It's surely a feat of technical prowess to play many short notes in rapid succession, but to play even a single note in a tone quality that people will want to listen to for several seconds at a time takes an entirely different set of skills — also honed over years — and if jazz music and small-town brass bands have taught us anything, it's that technical prowess in a brass player is no guarantee of tone quality.
|
||||||
|
|
||||||
|
So it is with simple incense. In a two or three ingredient build, all flaws are laid bare for insufflation and observation. While I'm pleased with this batch overall, there are some problems with these sticks. But first:
|
||||||
|
|
||||||
|
## The Build
|
||||||
|
|
||||||
|
|Ingredient|Grams|% of Build|
|
||||||
|
|----------|------|-----------|
|
||||||
|
|Western Red Cedar|8|58%|
|
||||||
|
|Hojari Frankincense Resin (Boswellia Sacra)|2.2|16%|
|
||||||
|
|Joss Powder (Litsea Glutinosa)|3.5|26%|
|
||||||
|
|
||||||
|
As I mentioned earlier, I'd read somewhere that using smaller quantities of resins can bring out their best; this held true. At 16%, the Hojari Frankincense really presents well after two weeks of curing. With B. Sacra, you know you've done well when those citrus and eucalyptus notes you're used to on the heater come out in the burn. At the ratios listed above, the cedar and frankincense are pretty well balanced: neither takes precedence over the other. If anything, I would prefer that the frankincense tip the scale ever so slightly in its favor; I might try 17-18% next time.
|
||||||
|
|
||||||
|
## Problems
|
||||||
|
|
||||||
|
Whoever fooled the world into thinking that [joss powder](https://scents-of-earth.com/joss-powder-litsea-glutinosa-superior-vietnam/) was "near odorless" is either hyposmic, a liar, or a hyposmic liar. It's not just the frankincense that sings in this build, so too does the joss. Admittedly, 26% is rather a large chunk of the total build for joss powder, so I only have myself to blame here.
|
||||||
|
|
||||||
|
[](/img/incense_seal.webp)
|
||||||
|
|
||||||
|
When you burn a properly executed Chinese style incense seal, the lack of binder leaves you with a very pure fragrance; one of the hallmarks of Yi-Xin's work is the near absence of any detectable binder, which gives the it that clean quality, as though you're burning an incense seal instead of a joss stick. I haven't managed to achieve this with my cedar and frankincense, largely because instead of patiently cutting slivers from my cedar plank and putting them through my hand-crank flour mill, I dumped a big chunk of it into my Vitamix dry container and let 2.2 peak horsepower of blender handle the matter. This resulted in a very fluffy cedar powder which was difficult to bind and even more difficult to extrude. (It's also bad practice in general because when you introduce heat via machine processing you begin to lose aromatics.) I do wonder whether the joss powder could by further reduced by using it in combination with a gum binder, such as tragacanth.
|
||||||
|
|
||||||
|
Irene of [Rauchfahne](https://blog.rauchfahne.de/en/) recommends resting your incense dough after hydration, which I also did not do. It seems that [patience](../patience/) does not come naturally to me.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
I'd burn this stuff on purpose. Problems aside, these sticks are plesant, good even, but they are not *great* in the same way as those made in middle-of-nowhere Hawaii by Yi-Xin's [onewheeling artisan](https://www.instagram.com/kencannata/reel/C5hslbrL9tB/). Feel free to give the build a try; get that binder down a bit, grind your cedar sensibly, and you may wind up with something special.
|
14
content/blog/cowsay_2024-01-02.md
Normal file
14
content/blog/cowsay_2024-01-02.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
title: Cowsay of the Day – Science
|
||||||
|
description: An ASCII cow postulates on the state of science education in the modern world.
|
||||||
|
date: 2024-01-02
|
||||||
|
tags:
|
||||||
|
- Cowsay of the Day
|
||||||
|
synopsis: An ASCII cow postulates on the state of science education in the modern world.
|
||||||
|
imageURL: /img/cowsayOfTheDay.avif
|
||||||
|
imageAlt: An ASCII cow with a thought bubble containing the word wut
|
||||||
|
mastodon_id: "111688829907363670"
|
||||||
|
---
|
||||||
|
As a big-old nerd, I spend a lot of time in the terminal on my computer. When you spend a lot of time somewhere, you want it to be comfortable. As a part of making my terminal more homey, I've set it up to give me a random quote each time I start a new session, delivered, of course, by a cow. Here's today's cowsay of the day:
|
||||||
|
|
||||||
|
{{ cowList.onScience | cowsay | safe }}
|
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: A Perplexing Failure
|
||||||
|
description: Failing to make the most delicious batch of incense ever.
|
||||||
|
date: 2024-04-13
|
||||||
|
tags:
|
||||||
|
- Incense
|
||||||
|
synopsis: My grand designs crumble as I fail to make the most delicious batch of incense ever devised.
|
||||||
|
imageURL: /img/_DSC0079_copy.avif
|
||||||
|
imageAlt: A small pile of short smooth brown incense sticks on a piece of MDF.
|
||||||
|
mastodon_id: "112266582201922869"
|
||||||
|
---
|
||||||
|
This January, I had grand visions for a sweet, gourmand batch of incense sticks. I'd start with a creamy base of sandalwood, combine it with plenty of guggul resin for that touch of caramel, a dash of warm cinnamon, and a sprinkling of sweet, vanillic tonka bean; these things were going to smell like dessert, like baking cookies, like your high-school English teacher's classroom when her most cloyingly sweet scented candle had been burning for the past four hours.
|
||||||
|
|
||||||
|
Things seemed to be going well while making the sticks; the dough smelled incredible, and extrusion was a dream — long, straight noodles that could be manipulated without breaking came one after another. I didn't sense that something might be wrong until I saw the sticks after they'd dried overnight, when I noticed that they were very smooth, compact, and *hard.* They didn't burn, either, which [isn't necessarily the death-knell](../patience) for a batch of incense sticks, but neither is it a good sign.
|
||||||
|
|
||||||
|
[](/img/_DSC0079_copy.avif)
|
||||||
|
|
||||||
|
It's now three months since the sticks were extruded; they haven't shown any signs of improvement, and I'm left scratching my head. The ingredient ratios in the build I used were based on those of a successful batch; by all estimations, this batch had everything it needed to combust! It may or may not smell nice, but surely, I thought, the batch will burn! Alas, my hubris was met with disappointment. Here's the build I used:
|
||||||
|
|
||||||
|
|Ingredient|Grams|% of Build|
|
||||||
|
|----------|------|-----------|
|
||||||
|
|Tonka Bean|0.3|5%|
|
||||||
|
|Cinnamon|0.75|11%|
|
||||||
|
|Guggul Resin|1.5|23%|
|
||||||
|
|Sandalwood|3|46%|
|
||||||
|
|Joss Powder (Litsea Glutinosa)|1|15%|
|
||||||
|
|
||||||
|
My best guess as to why this build didn't work out has to do with the cinnamon. I know that some cinnamon varieties are mucilaginous, producing a mucilage (plant slime), when mixed with water. Knowing that gum binders, such as xanthan gum, can cause combustion issues in incense when used in higher concentrations, I suspect that the combination of 15% joss powder plus another 11% of the mucilaginous cinnamon somehow bound the sticks too tightly, preventing combustion.
|
||||||
|
|
||||||
|
[](/img/_DSC0014_copy.avif)
|
||||||
|
|
||||||
|
Hypotheses aside, I may never know why exactly this build failed. It's always a shame when a batch turns out to be a complete flop after you've put so much time into carefully grinding and sifting precious aromatics, then kneading, extruding and drying neat little noodles of incense — waiting weeks or months to see whether your hopes for them have come to fruition. But all is not lost after all: when I break up the sticks into small pieces and put them on my mini ~~circuit board,~~ erm, *incense* heater, the fragrance is everything I had thought it might be. I'll content myself with that as I wait for yesterday's batch of rose and myrrh to cure.
|
||||||
|
|
66
content/blog/kde-plasma-side-panel.md
Normal file
66
content/blog/kde-plasma-side-panel.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
title: Setting up a Toggleable Side Panel in KDE Plasma 6
|
||||||
|
description: Creating a Raven-like side-panel in KDE Plasma 6 that can be toggled with a click using native plasma panels and Scriptinator.
|
||||||
|
synopsis: Creating a Raven-like side-panel in KDE Plasma 6 that can be toggled with a click using native plasma panels and Scriptinator.
|
||||||
|
date: 2024-03-18
|
||||||
|
tags:
|
||||||
|
- GNU/Linux
|
||||||
|
- KDE
|
||||||
|
imageURL: /img/sidePanel/sidePanel_copy.avif
|
||||||
|
imageAlt: A cropped screenshot of my plasma desktop showing a side-panel on the right side of the screen containing the clipboard history widget and the media player widget. On the bottom panel is the Scriptinator plugin, showing a tooltip the following title: "Show Panel," and body text: "Show the hidden right panel."
|
||||||
|
mastodon_id: "112119633092992081"
|
||||||
|
---
|
||||||
|
Since a brief tryst with [Ubuntu Budgie Edition](https://ubuntubudgie.org/), I've dearly missed its Raven side-panel, a special panel on the side of the screen that can be opened and closed with a click. As someone who *needs* a clean, minimal desktop, the workflow is just too perfect — when you have two or three widgets that you use frequently, but not frequently enough that they warrant permanent homes on a main panel, just stuff them into a disappearing side-panel that can be called with a quick key-combination or by clicking on an icon; It's a great way to keep things out of the way, but within reach, without having a permanently cluttered system tray that you might want to keep clear for things like email notifications.
|
||||||
|
|
||||||
|
[](/img/sidePanel/sidePanel_copy.avif)
|
||||||
|
|
||||||
|
There are some drawbacks; this workflow isn't well supported on KDE Plasma, so it's a bit of a faff to set up, and only a few widgets will display nicely on a wide side-panel. For instance, it would be a dream to have the KDE weather widget automatically take advantage of the horizontal space and display the information that would usually be in its dropdown, but what you get instead is a giant icon, for now at least. I use my side-panel for my clipboard history and the media player widget, both of which play nicely with a side-panel. Another niggle I have with it is that, as far as I know, there's no way to disable activation of the panel when your mouse pointer makes contact with the screen edge. This is a mild to moderate inconvenience when you're working with applications that have toolbars on the sides of the window, like design applications often do.
|
||||||
|
|
||||||
|
For me, personally, the drawbacks aren't so severe as to put me off of the workflow.
|
||||||
|
|
||||||
|
## Creating and configuring the panel
|
||||||
|
First, you'll need to create a panel. To do this, right click on an empty section of your desktop, and select "Add Panel > Empty Panel." When the panel appears, right click it and select "Enter Edit Mode." Set up your panel however you like, but you will need to set "Visibility" to "Auto Hide" and may want to give it a width of at least 400px or so.
|
||||||
|
|
||||||
|
[](/img/sidePanel/panelSettings_copy.avif)
|
||||||
|
|
||||||
|
## Setting up the script
|
||||||
|
Now, if you wanted to show and hide your panel with a keyboard shortcut, you can set up a focus shortcut in the panel settings window and stop here. If, like me, you want to toggle your panel by clicking on an icon somewhere, we're going to have to use a wee script, but don't worry, it's not as hard as it sounds and I'll take you through it step by step.
|
||||||
|
|
||||||
|
Before we can put our script together, we're going to need to know what the ID of our panel is. Open up KRunner with Alt+F2 or Alt+Space and run `plasma-interactiveconsole`. This will launch KDE's Desktop Shell Scripting Console. In the console, type `print(panelIds);` and click "Execute." Assuming you entered that in correctly, what you should see now in the output console beneath the text editor is a series of numbers — the ID numbers of our panels. Keep a note of these numbers.
|
||||||
|
|
||||||
|
[](/img/sidePanel/printIDs_copy.avif)
|
||||||
|
|
||||||
|
Clear the text editor and enter the following:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let panel = panelById(401);
|
||||||
|
|
||||||
|
panel.hiding === "autohide" ? panel.hiding = "windowsgobelow" : panel.hiding = "autohide";
|
||||||
|
```
|
||||||
|
This will check if our panel is set to auto-hide; if it is, the script will set the panel to "windows go below" mode, otherwise it will set the panel to auto-hide.
|
||||||
|
|
||||||
|
Now to make use of those panel ID numbers. Which number corresponds to your new side-panel? While I can't be sure, chances are it's the last number on the list as we've just made the new panel a moment ago. So in the script above, where I have entered 401, enter the last number in your ID list and click "Execute." At this point, if the ID number is correct, your panel should appear; click "Execute" once more to hide it.
|
||||||
|
|
||||||
|
## Setting up the Scriptinator widget
|
||||||
|
Alright, we've got our script ready, so we just need one more thing in place: a button or icon that we can click on to show and hide the panel. Fortunately, we can use a widget called "Scriptinator" to provide just this. Right click on an empty area of your desktop or a panel, click "Add Widgets," and "Get New Widgets."
|
||||||
|
|
||||||
|
[](/img/sidePanel/getWidgets_copy.avif)
|
||||||
|
|
||||||
|
From here, find and install Scriptinator. Once installed, simply drag it where you'd like it to live, either on your desktop, or on a panel. Once you've done that, right click on the widget and choose "Configure Scriptinator." Here, enter the path of the icon you'd like to use in "Custom icon full path;" I used `/usr/share/icons/breeze-dark/actions/22/sidebar-expand-right-symbolic.svg`. In "OnClick Script," enter the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qdbus org.kde.plasmashell /PlasmaShell evaluateScript ''
|
||||||
|
```
|
||||||
|
and between those single-quote marks, paste in the full script we put together in the Desktop Shell Scripting Console, like this:
|
||||||
|
```bash
|
||||||
|
qdbus org.kde.plasmashell /PlasmaShell evaluateScript 'let panel = panelById(401);
|
||||||
|
|
||||||
|
panel.hiding === "autohide" ? panel.hiding = "windowsgobelow" : panel.hiding = "autohide";'
|
||||||
|
```
|
||||||
|
|
||||||
|
[](/img/sidePanel/Scriptinator_copy.avif)
|
||||||
|
|
||||||
|
Set up a tooltip if you like, hit apply, and test out your toggle button.
|
||||||
|
|
||||||
|
## Success!
|
||||||
|
If you've done everything correctly, you should see your side-panel appear when you click the widget and disappear when you click a second time. You may need to restart to see your icon applied to the widget; if you don't want to wait, you can drop the file path into "OnClick icon full path" in your Scriptinator configuration.
|
21
content/blog/kheouns-blend.md
Normal file
21
content/blog/kheouns-blend.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: "Incense Review: Kheoun’s Blend"
|
||||||
|
description: Reviewing Kheoun’s Blend by The World Makes Scents.
|
||||||
|
date: 2024-06-23
|
||||||
|
tags:
|
||||||
|
- Incense
|
||||||
|
- Incense Review
|
||||||
|
synopsis: Reviewing Kheoun’s Blend by The World Makes Scents.
|
||||||
|
imageURL: /img/kheouns-blend-incense-sticks.webp
|
||||||
|
imageAlt: A partially opened box of incense sticks
|
||||||
|
mastodon_id: "112668846624633338"
|
||||||
|
---
|
||||||
|
There is a reason that much incense on the market makes extensive use of fragrance oils: it's simply easier. As returning readers [will know,](https://nathanupchurch.com/blog/gourmand-sandalwood-incense-a-perplexing-failure/) blending combustible incense made with plain old plants is extraordinarily difficult, even when only using two or three ingredients. [The World Makes Scents](https://theworldmakesscents.com/) is a Chicago-based incense maker that does just that. Their Kheoun's Blend incense sticks are based on a unique blend of plants introduced to the team by Kheoun, [a traditional incense maker based in Cambodia](https://blog.rauchfahne.de/en/2023/07/18/the-world-makes-scents-en/).
|
||||||
|
|
||||||
|
[](/img/kheouns-blend-incense-sticks.webp)
|
||||||
|
|
||||||
|
The twelve sticks in my order came extremely well packaged, in a sturdy paper-over-board box with with a layer of batting both on top and beneath the sticks to prevent breakage in the post. The back of the box describes the product within as "Incense as it was made for thousands of years before industrialization and greed." I'm all for it. The hand-extruded, coreless sticks are light brown in color, roughly 185mm long, very thick at around 3.5mm, and just a little wiggly. There is a sweet, baking-spice fragrance on the unlit sticks.
|
||||||
|
|
||||||
|
I've long felt that you can't get a proper impression of the character of a stick of incense before the first ash has fallen. Often, incense briefly smells quite harsh when initially lit. Interestingly, in that first half-second of lighting, these sticks briefly emit a rather pleasant campfire / wood smoke scent. As the stick begins to burn in earnest, however, I'm met with an almost Tibetan herbaceousness, a stevia-leaf like sweetness, notes of cinnamon, anise, and sage, with occasional wafts of something bright and camphorous.
|
||||||
|
|
||||||
|
Overall the fragrance is very pleasant. Subdued baking-spice notes sit alongside a sage-like herbaceousness atop a mellow, ever-present sweetness, which is lifted by a blink-and-you'll-miss-it camphor note. Excellent temperature modulation keeps this blend very fragrant throughout; smoke production is modest for a stick of this size, and the fragrance in the burn is very much like that of the unlit stick. This is a very interesting stick unlike anything I've burned before; absolutely worth experiencing, and a great incense to burn in the living room when you have company.
|
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: "Learning to Love Myrrh: Myrrh & Rose Incense"
|
||||||
|
description: "I finally figure out how to make myrrh work in a composition."
|
||||||
|
date: 2024-08-05
|
||||||
|
tags:
|
||||||
|
- Incense
|
||||||
|
synopsis: "I finally figure out how to make myrrh work in a composition."
|
||||||
|
imageURL: /img/pexels-david-roberts-940521-8323579.webp
|
||||||
|
imageAlt: A beautiful light-pink dog rose, rosa canina.
|
||||||
|
mastodon_id: "112909867440319574"
|
||||||
|
---
|
||||||
|
Myrrh can be a challenging note. I've seen the resins collected from various members of the Commiphora genus described as everything from loamy, bitter, and mushroomy, to reminiscent of cleaning fluid or a dental clinic; whatever impression you take from the fragrance of myrrh resin, there's no denying that it's unique; there is no mistaking myrrh. While I'm rarely a fan of myrrh alone, or as the predominant note in a sparse composition, I've always felt that there is something compelling about it. Despite its overall unpleasantness, I find heated myrrh resin to produce a dark, mysterious, and somehow sexy fragrance. When balanced well, such as in *Mystic Jade* from Shoyeido's *Magnifiscents* collection, it adds a wonderful, earthy warmth to a composition that's hard to beat.
|
||||||
|
|
||||||
|
For some time I've struggled to incorporate myrrh into a stick that I can be proud of; it isn't a resin that you can just drop into a composition with the expectation that it'll work. My experiments with commiphora kua, opoponax, and wightii have all ended in disappointment… until recently.
|
||||||
|
|
||||||
|
I've long had an inkling that myrrh would pair well with rose. It's challenging to incorporate flowers into combustible incense; some say it's close to impossible to do without winding up with an incense that smells of acrid burning plant material with, if you're lucky, a touch of whatever flower you've added. Certainly, I have realized that often other methods of incorporating floral fragrances work best. Recently I have discovered, however, that if you start with very good material, and methodically try varying ratios in a series of trail-burning tests, you may wind up surprised by how close a fragrance you can achieve to the fragrance of fresh flowers while minimizing acrid notes. For instance, in my testing, I found that a combination of 30% Rosa Canina and 70% Santalum Spicatum, both very high quality powders given to me by [Dave of *The World Makes Scents*,](../visiting-chicago-incense-maker-dave-of-the-world-makes-scents/) smells absolutely wonderful.
|
||||||
|
|
||||||
|
[")](/img/pexels-david-roberts-940521-8323579.webp)
|
||||||
|
|
||||||
|
|
||||||
|
With that knowledge, I composed a stick featuring myrrh, rose, and sandalwood. While the build isn't perfect, I'm already really enjoying the small batch of sticks that I made only a few days ago. The fragrance is gentle and powdery, with a hint of smoke, a soft rose note and that fruitiness that occurs when rose petals are heated. Benzoin lends a subtle sweetness alongside the sandalwood, while the myrrh adds it's unmistakable fragrance and a bittersweet molasses note. The whole ensemble is lifted and brought together by a smidgen of borneol camphor. The myrrh reduces the need for binders, so I've gone with a weak binder, acacia gum, which also helps to firm up and strengthen the extruded sticks once dried.
|
||||||
|
|
||||||
|
## The Build
|
||||||
|
Note that this is a test build that produces less than four grams of dough; you may want to double the amount.
|
||||||
|
|
||||||
|
|Ingredient|Grams|% of Build|
|
||||||
|
|-|-|-|
|
||||||
|
|Myrrh Resin (Commiphora Kua) |0.2|5.6%|
|
||||||
|
|Rose Petal (Rosa Canina)|1|27.9%|
|
||||||
|
|Sandalwood (Santalum Spicatum)|2|55.9%|
|
||||||
|
|Benzoin Siam|0.14|3.9%|
|
||||||
|
|Acacia Gum|0.2|5.6%|
|
||||||
|
|Borneol Camphor|0.04|1.1%|
|
||||||
|
|
||||||
|
## Thoughts
|
||||||
|
|
||||||
|
I really like this stick, but I do think that it could stand some improvement. Some spices might round out the profile a little, maybe a little clove and cinnamon. It's not the *cleanest* fragrance in the world, likely due to both the myrrh and the high ratio of flowers, but I have been finding it incredibly moreish nonetheless. I hope someone will try to make this and let me know their thoughts!
|
17
content/blog/let-us-waffle.md
Normal file
17
content/blog/let-us-waffle.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: Let Us Waffle
|
||||||
|
description: Tools like cooked.wiki let us strip away the cruft from online recipes. Is this necessarily a good thing?
|
||||||
|
date: 2024-01-24
|
||||||
|
tags:
|
||||||
|
- Vegan Cooking
|
||||||
|
- Quick Thoughts
|
||||||
|
synopsis: Tools like cooked.wiki let us strip away the cruft from online recipes. Is this necessarily a good thing?
|
||||||
|
imageURL: /img/pexels-brigitte-tohm-378008_compressed.webp
|
||||||
|
imageAlt: An oddly rectangular waffle covered in raspberries. It actually looks quite dry and not very nice. Hopefully there's some syrup on the side!
|
||||||
|
mastodon_id: "111812478768090324"
|
||||||
|
---
|
||||||
|
So, about this [cooked.wiki](https://cooked.wiki) thing, believe me when I say I take my fair share in our collective frustration as I find myself skimming through a hugoesque tome on Brayden and Braxlynne’s wiggly teeth in order to reach the ingredients for “Keighleigh’s Extra Easy No-Bake Ten Minute Palmiers (*So Delicious You’ll Snort the Crumbs!*),” but I must admit that the endless complaining about it puts me out a bit. Here’s the thing; as someone who writes for his own personal blog, who plans to someday publish a recipe or two, *the waffling is the point.*
|
||||||
|
|
||||||
|
Keighleigh’s recipe blog isn’t a cookbook. No one is paying a subscription fee to access her recipes, nor is her little wordpress site a public amenity. If Keighleigh is anything like me, she writes for the joy of it. Our hypothetical author here may not really be setting out to write *recipes* at all. In all likelihood, she sees herself as writing prose about her life and the things she enjoys talking and thinking about, while also taking the time to record her recipes for the benefit and enjoyment of the reader who stumbles across her home on the internet. Only wanting to share what brings her joy with others, she doesn’t ask much – so why not just skim? Is it really such a tall order?
|
||||||
|
|
||||||
|
Now I’m not naïve; there are surely authors out there inflating their wordcount for SEO purposes, hoping to eke out an extra dollar or two from a smattering of banner ads and affiliate links. I know there are accessibility concerns also. Valid reasons abound for using a tool like Cooked; I even took the time to add [structured data](https://yoast.com/what-is-structured-data/) to my website to help others use my work in interesting and helpful ways. There’s just something downright bleak, however, about seeing the meat of my prose shelled wholesale by a piece of proprietary software, stripped down to mere data, devoid of all context and humanity. While I’m not suggesting that no one should use tools like Cooked; I am asking that they maybe consider the author first. Besides that, instant gratification can be rather ungratifying – isn’t fiddling with the shell half the fun of eating a pistachio?
|
29
content/blog/new-kmines-themes.md
Normal file
29
content/blog/new-kmines-themes.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: Making Two New High Contrast Themes for KMines
|
||||||
|
description: My first KDE contribution! Two new high-contrast KMines themes that will arrive with Plasma 6.
|
||||||
|
date: 2024-01-20
|
||||||
|
tags:
|
||||||
|
- KDE
|
||||||
|
- FOSS/FLOSS
|
||||||
|
synopsis: My first KDE contribution! Two new high-contrast KMines themes that will arrive with Plasma 6.
|
||||||
|
imageURL: /img/kmines_dark.webp
|
||||||
|
imageAlt: A screenshot of the KMines game window showing a new dark theme.
|
||||||
|
mastodon_id: "111794936518292495"
|
||||||
|
---
|
||||||
|
## Why KMines?
|
||||||
|
Minesweeper is a tragically underrated puzzle game. While I recall examining the mysterious array of gray squares as a child, it wasn't until adulthood that I took the time to learn the rules of the game. Despite my late start, however, I still count minesweeper as a classic. These days, good minesweeper clones are hard to come by. I settled on GNOME's [Mines](https://wiki.gnome.org/Apps/Mines) for a while, but as the look of GTK applications on my QT-based [KDE Plasma Desktop](https://kde.org/plasma-desktop/) sets my teeth on edge, I ditched it for [KMines](https://apps.kde.org/kmines/) in short order. While I enjoyed the game, I found the themes shipped with KMines a bit dated, so I thought I'd make my own.
|
||||||
|
|
||||||
|
[](/img/kmines_dark.webp)
|
||||||
|
|
||||||
|
## The Drama
|
||||||
|
I didn't quite know what I was getting into when I started working on my themes. I had expected there'd be a simple way to add themes through the KMines settings menu, or by dropping an SVG somewhere in your file-system. If only it were so simple. Adding a theme to KMines requires setting up a full-on KDE development environment, re-compiling KMines from source each time you want to test it, and then, of course, submitting a merge request to the git repository. Thanks to the help of some very patient souls in various KDE Matrix channels, I was able to work through all of this, but I found the process so tricky that I submited a second merge request, this time to the repository for [develop.kde.org](develop.kde.org), for [a page documenting the process](https://develop.kde.org/docs/apps/kmines/theme/). Now I realise that some developers out there are going to read through this and wonder if I was dropped on my head as an infant, but in my defense, when it comes to software development, I'm a humble designer and Jamstack web developer. This is all very new to me, and I was expecting a much more streamlined process for what I saw as simple visual tweaks.
|
||||||
|
|
||||||
|
[](/img/kmines_light.webp)
|
||||||
|
|
||||||
|
## Why High Contrast
|
||||||
|
When submitting my initial design, a KDE contributor who had been helping me via Matrix pointed out that they found the theme difficult to parse visually as the contrast was quite low. I hadn't considered contrast ratios here, my thinking being that I didn't need to; I was just making one theme among many, after all, and users could choose any theme that worked best for them. After some consideration, however, it dawned on me that my theme would likely be the only modern-looking theme in the release, so it would be ideal if it were as accessible as possible. With this in mind, thanks to the feedback of one helpful individual, instead of one pretty but low contrast theme, I decided to make two modern high contrast themes, one light and one dark, targeting the WCAG AAA standard for contrast.
|
||||||
|
|
||||||
|
## Coming in Plasma 6!
|
||||||
|
With thanks to those KDE contributors who helped make it happen, the merge request containing these two themes scraped by the skin of its teeth past the closing door of a feature-freeze and will be available with Plasma 6 this February!
|
||||||
|
|
||||||
|
|
16
content/blog/offline.md
Normal file
16
content/blog/offline.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: Offline
|
||||||
|
description: A conversation with a colleague caused me to consider the consequences of online-only tooling.
|
||||||
|
date: 2023-10-20
|
||||||
|
tags:
|
||||||
|
- Quick Thoughts
|
||||||
|
synopsis: A conversation with a colleague caused me to consider the consequences of online-only tooling.
|
||||||
|
imageURL: /img/kenny-eliason-uq5RMAZdZG4-unsplash.webp
|
||||||
|
imageAlt: A server rack in the dark with colorful cables draped between the ports of servers and switches.
|
||||||
|
mastodon_id: "111268603361637013"
|
||||||
|
---
|
||||||
|
As part of a project investigating a potential new piece of software, I've been speaking with colleagues and contractors to determine which features they rely on to do their day-to-day tasks, as well as discover any wish-list items for a new platform. In one of these discussions with a colleague, we had covered her relatively simple use case, and moved on to discuss potential features that might be useful. At this juncture, she mentioned, somewhat apologetically, that should a particular workflow be translated to a new platform, it was important to be able to access data and documents offline.
|
||||||
|
|
||||||
|
I've long been irritated by the propensity of modern proprietary software to lock in its users with monthly subscriptions and online-only access, allowing them to shut off the supply, as it were, at a moment's notice should a billing cycle fail to complete to their satisfaction, or should they be momentarily unable to subject their user to data collection or surveillance. I'm also no fan of slow, web-based software devouring gigabyte after gigabyte of memory, while requiring at least two machines to operate — a client, and a server. Still, I was struck by my colleague's comment; it seems rare these days that your average computer user has any expectation left that *anything* ought to work without access to the internet. It's a travesty, really.
|
||||||
|
|
||||||
|
Compared to the experience of opening Adobe Illustrator or even Outlook on the computer issued by my day-job, the results of switching my personal machines and the workflows for my small business to exclusively FLOSS tooling has felt liberating — no waiting at splash-screens, advertising baked into the OS, or fans screaming from the combined load of endless electron apps competing for their share of system resources; best of all, from spreadsheets, to my IDE, to design files, in order to get work done, not a damned thing requires me to be forever online.
|
19
content/blog/patience.md
Normal file
19
content/blog/patience.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: Patience
|
||||||
|
description: Learning about patience through an incense-making miscalculation.
|
||||||
|
date: 2024-01-10
|
||||||
|
tags:
|
||||||
|
- Quick Thoughts
|
||||||
|
- Incense
|
||||||
|
synopsis: Learning about patience through an incense-making miscalculation.
|
||||||
|
imageURL: /img/dragons_blood_incense_copy.avif
|
||||||
|
imageAlt: A small piece of a coreless, Japanese-style incense stick burning in a black cast-iron burner.
|
||||||
|
mastodon_id: "111732713202024407"
|
||||||
|
---
|
||||||
|
Some time ago, maybe a year or so, I extruded a batch of incense sticks from some ingredients I thought might go well together: sandalwood, cinnamon, dragon's blood resin, a touch of Hojari frankincense for acidity, and some tonka bean for sweetness, if I recall correctly. After leaving the sticks to dry overnight, I was disappointed to see that they didn't stay lit; the stick would shrink behind the ember, and it would fizzle out in short order. Even worse, the little scent I was able to detect during the short burn was terrible: acrid and smoky. Dejected, I put the sticks away, returning to attempt to burn a small fragment every few days or so before I lost interest entirely.
|
||||||
|
|
||||||
|
A few months later, the tube of crooked red incense sticks caught my eye, and I once again attempted to burn a stick. To my surprise, it stayed lit throughout the entire burn. The fragrance had transformed also, from leafy-campfire to a simple, warm, slightly sweet, and medicinal fragrance. While this was enough of an improvement to encourage me to light one every now and then, I remained disappointed that the fragrance was so far from what I'd hoped to achieve. After half-heartedly burning each stick in the little plastic tube that housed them over a period of weeks, the tube disappeared into a basket on the shelf beneath my coffee table amidst a mess of bundled cables and game-controllers, never to be seen again – until just a few days ago.
|
||||||
|
|
||||||
|
[](/img/dragons_blood_incense_copy.avif)
|
||||||
|
|
||||||
|
While rustling around in search of a controller, I discovered the thin plastic tube, noticing two small fragments of incense sliding about as I lifted the tube from the basket. As I lit the first fragment this morning, I was met with a wonderfully clear impression of dragon's blood, uplifted by the bright citrus of Hojari frankincense, on a sweet, warm, woody base; my incense had turned out well after all. Unfortunately, the recipe, written on the tube in dry-erase marker, had long worn off; thinking the batch was a failure, I hadn't recorded it anywhere else. Burning those last two fragments today was bittersweet; all I had needed to do was wait. I'm frustrated about a number of things here, but there is something oddly gratifying about the situation. By failing to record the recipe, I got to experience something rare and unique today. In those peaceful, fragrant moments, I experienced something lovely for the first and last time — and I learned a thing or two about patience.
|
26
content/blog/poison.md
Normal file
26
content/blog/poison.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: "Poison"
|
||||||
|
description: "Reflecting on eating, and cooking, habits in light of Pete Wells' stepping down from his role as Times restaurant critic."
|
||||||
|
date: 2024-07-16
|
||||||
|
tags:
|
||||||
|
- Quick Thoughts
|
||||||
|
- Restaurants
|
||||||
|
- Vegan Cooking
|
||||||
|
synopsis: Reflecting on eating, and cooking, habits in light of Pete Wells' stepping down from his role as Times restaurant critic."
|
||||||
|
imageURL: /img/pexels-davideibiza-1771809.webp
|
||||||
|
imageAlt: Amber glass bottles with poison warnings.
|
||||||
|
mastodon_id: "112798801312124662"
|
||||||
|
---
|
||||||
|
I recently happened across the article in which Pete Wells [announces that he will be stepping down](https://www.nytimes.com/2024/07/16/dining/pete-wells-steps-down-food-critic.html?unlocked_article_code=1.7k0.R2zu.sGv5x7hNrfba) from his role as Times' restaurant critic. Health stood tall among the reasons behind the decision:
|
||||||
|
|
||||||
|
> My scores were bad across the board; my cholesterol, blood sugar and hypertension were worse than I’d expected even in my doomiest moments. The terms pre-diabetes, fatty liver disease and metabolic syndrome were thrown around.
|
||||||
|
|
||||||
|
I've been having a similar reckoning. In 2013 I turned vegan. At the time, this meant that you had better either learn to cook or learn to love whatever meager offerings you were lucky enough to find in the miniscule corner of your local grocery store reserved for you and the other crusties: gluten deniers; allergy sufferers; and so on. Rather than endure the pasty, sour blocks of potato starch that then passed for 'vegan cheese', I started to develop my cooking skills in earnest, and not just at home; I would work in kitchens, never as a cook, but as a cocktail bartender and later a barista, both at places upscale enough to warrant cocktail or coffee stations in the kitchen. I learned a lot from those places, especially in the cocktail bar where the chef and I practically stepped on each others toes. From the talented people in that kitchen, I learned what it was to deglaze a pan, how to balance salt, fat, and acid in a dish, and that a dish is over-seasoned when you can *feel* the salt on your tongue. Through these experiences, and through consistent practice at home, I became a very competent home cook.
|
||||||
|
|
||||||
|
Using what I've learned in kitchens and from my own practice and research, I can make a Beyond Meat / Impossible style ground beef analogue at home. I can make a competent béchamel sauce with home-made cultured vegan butter, throw together a romesco, red sauce, or salsa verde at a moment's notice, and whip up fresh falafel, tamales, mushroom stroganoff, a Thai green curry, or South Indian sambar on a weeknight without so much as looking at a recipe. All of it poison, I increasingly realize.
|
||||||
|
|
||||||
|
Restaurants are places where the preparation of food, necessary to sustain life, meets with a profit motive. In order to retain custom, restaurant dishes are designed with aspirations of meeting the zenith of human tastes, forged thousands of years ago when scraping together sufficient calories of plant and animal matter to see one through the day was no mean feat. If you are among the lucky sliver of the human population that can participate in today's world of plenty, however, restaurant food doesn't only fill those key nutritional silos that drive the most intense of cravings; it utterly overwhelms them, and the consequences from overindulging can be dire.
|
||||||
|
|
||||||
|
Learning to cook this way has done me a tremendous disservice. The talented people who I've learned from over the years have inculcated into me, as gospel truth, techniques to create food that delights the palate and utterly destroys the body.
|
||||||
|
|
||||||
|
Since giving up smoking, I've learned that the craving never really leaves you entirely. As I glance down at my beautiful little marble pinch-bowl piled high with large white crystals of kosher salt, I wonder whether this craving will never truly leave me also?
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: How to Transfer Files Securely with the “scp” Command
|
title: How to Transfer Files Securely with the “scp” Command
|
||||||
description: How to transfer files to and from a remote Server with the scp / secure copy command
|
description: How to transfer files to and from a remote Server with the scp / secure copy command.
|
||||||
synopsis: How to transfer files to and from a remote Server with the scp / secure copy command
|
synopsis: How to transfer files to and from a remote Server with the scp / secure copy command.
|
||||||
date: 2023-06-12
|
date: 2023-06-12
|
||||||
tags:
|
tags:
|
||||||
- GNU/Linux
|
- GNU/Linux
|
||||||
|
46
content/blog/switching-to-gnu-linux-mentally.md
Normal file
46
content/blog/switching-to-gnu-linux-mentally.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: "Switching to GNU/Linux: Mentally"
|
||||||
|
description: The mindset shift that produces happy users of GNU/Linux and other Free/Libre and Open Source Software.
|
||||||
|
date: 2024-06-11
|
||||||
|
tags:
|
||||||
|
- GNU/Linux
|
||||||
|
- FOSS/FLOSS
|
||||||
|
- KDE
|
||||||
|
synopsis: The mindset shift that produces happy users of GNU/Linux and other Free/Libre and Open Source Software.
|
||||||
|
imageURL: /img/pexels-ds-stories-9228363_copy.webp
|
||||||
|
imageAlt: A very cute photo by DS stories on pexels.com of a little pink brain-shaped candle on a light blue background.
|
||||||
|
mastodon_id: "112615355114722806"
|
||||||
|
---
|
||||||
|
[Stallman was right;](https://www.benzinga.com/news/24/06/39219971/edward-snowden-echoes-richard-stallmans-warnings-on-proprietary-software-after-user-says-adode-can-n) in the wake of Microsoft's announcement of its [much-maligned](https://www.wired.com/story/microsoft-recall-off-default-security-concerns/) Recall feature and widespread public backlash to the [terms and conditions](https://www.benzinga.com/top-stories/24/06/39209804/it-is-time-to-cancel-adobe-new-photoshop-terms-conditions-spark-outrage-among-professionals) for Adobe Creative Cloud products, it's clear that trust in big tech and the software it produces is rapidly eroding. Under the circumstances, it's no surprise that Free/Libre and Open Source Software (FLOSS) is seeing an uptick in interest from the public at large. So as ever more average users consider "switching to Linux," it strikes me that while there exist tomes on the technical aspects, there seems to be much less written on the shift in thinking that is part and parcel of every experienced and well-adjusted FLOSS user. So if you're making the switch or know someone who is, here's some advice to make the most of the transition.
|
||||||
|
|
||||||
|
[")](/img/rms.jpg)
|
||||||
|
|
||||||
|
## Welcome
|
||||||
|
|
||||||
|
First of all: welcome to GNU/Linux! You've chosen the operating system [that powers](https://www.vrogue.co/post/linux-is-everywhere-infographic) bullet trains, the world's fastest supercomputers, U.S.A. air traffic control, CERN's Large Hadron Collider, and Google, Amazon, and Microsoft's cloud services, [used by](https://en.wikipedia.org/wiki/List_of_Linux_adopters) NASA, the People's Liberation Army, the Turkish government, whitehouse.gov, the U.S.A. Department of Defense, France's national police force, ministry of agriculture, and parliament, Iceland's public schools, the Dutch Police Internet Research and Investigation Network, Burlington Coat Factory, Peugeot, DreamWorks Animation, the Chicago Mercantile Exchange, the London Stock Exchange, the New York Stock Exchange, and [Stephen Fry.](https://en.wikipedia.org/wiki/List_of_Linux_adopters#Celebrities)
|
||||||
|
|
||||||
|
As you've no doubt inferred by now, GNU/Linux users span from your everyday cat-video viewer to large institutions and organizations where operating system reliability and performance mean the difference between life and death. No matter where you are on this spectrum, with a little humility, open-mindedness, and perseverance, I promise that you can find your self every bit as happily at-home with GNU/Linux as you were with whatever OS you've been using up to this point. This may mean giving up a long-trusted piece of software for something new and different, but for many new users the most hard-won battle is a change in mentality.
|
||||||
|
|
||||||
|
## You're not a power-user anymore
|
||||||
|
|
||||||
|
I've heard it said that the most "computer literate" people often find it especially arduous to adjust to GNU/Linux. I've been there; it's a frightening thing to go from the person family, friends, and neighbors call to help with problems with any device that has so much as an LED on it to feeling like that clueless relative with a dozen toolbars installed on their outdated version of Internet Explorer. The reality is that while you've gotten very good at navigating the operating system that you've been using for the past twenty years, very little of that knowledge is useful in GNU/Linux. This is something you're going to have to accept early on: no matter what distro you choose, it's going to be different to Windows or MacOS in very fundamental ways.
|
||||||
|
|
||||||
|
This means that, no matter your mastery of Windows keyboard shortcuts, or how convoluted your [AutoHotkey](https://www.autohotkey.com/) config may be, it's going to take you some time to grasp the basics. Beyond that, the bar to become a GNU/Linux power-user is much, much higher than it is on proprietary operating systems. In case you're feeling intimidated, know that this comes with some serious advantages. GNU/Linux systems come with a practically limitless potential for mastery, efficiency, and customization. In time, you'll be able to customize your GUI to your exact specifications, automate system maintenance, and knock out common tasks with a speed you wouldn't have thought possible on your old OS.
|
||||||
|
|
||||||
|
## Embrace the new
|
||||||
|
|
||||||
|
Switching to GNU/Linux is, in some ways, much more convenient than switching from, say, MacOS to Windows. Chiefly, most distros can be configured to run a wide range of software built for MacOS, Windows, or Android with minimal fuss. That said, I strongly encourage new users to explore FLOSS alternatives built on and for GNU/Linux. FLOSS projects often get a bad rap among users of proprietary operating systems because while a piece of software may run on these systems, the experience is rarely as good as it is on the system is was designed for: usually, GNU/Linux. FLOSS mainstays such as [LibreOffice,](https://www.libreoffice.org/) [Krita,](https://krita.org/en/) [Inkscape,](https://inkscape.org/) [Scribus,](https://www.scribus.net/) [Kdenlive,](https://kdenlive.org/en/) and [Ardour](https://ardour.org/) are at their best on GNU/Linux in terms of appearance, performance, and features. There are professionals of every stripe who do their work with an exclusively FLOSS toolset, from graphic design to video editing, audio production, data analytics, and more. If they can do it, so can you! Don't let the one piece of proprietary software that just won't work put you off of your new operating system when there's a whole new ecosystem of incredible software to explore.
|
||||||
|
|
||||||
|
[](/img/scribus_copy.webp)
|
||||||
|
|
||||||
|
New users of FLOSS projects often complain that the user interface or workflow of the tool they're trying is "unintuitive." Occasionally, these complaints hit on an area that genuinely could use some improvement, but more often, new users are simply expressing frustration that the workflow of a FLOSS project is different from what they are used to. These applications are not mere clones of their proprietary counterparts; they are projects in their own right, with unique goals, ideals, features, and workflows. Getting through a work project a little more slowly at first is not necessarily a flaw in the tool, it likely just means that you need a bit more practice. In time, you'll come to learn and appreciate killer features that go above and beyond the capabilities of software produced by even the largest tech companies.
|
||||||
|
|
||||||
|
## As a GNU/Linux user, you're part of a community
|
||||||
|
|
||||||
|
When you switch to GNU/Linux, you're not a customer any more. FLOSS projects are largely build by communities of volunteers who work on what they find interesting or important for their own reasons. There's no support line to call, no one to complain to if something breaks, and no one is losing anything by you choosing not to use their software. If you need help, or if you want to help make a FLOSS project better, you're going to have to engage with the wider community. Every project has a forum, a Matrix or IRC channel, or some other means of connecting users and developers. If you have a problem you can't solve on your own, these are the places to go to get help. Sign up and make a good faith effort to learn the rules and etiquette of the community, and chances are someone will be more than willing to help you find a solution out of sheer civic-mindedness.
|
||||||
|
|
||||||
|
There is likewise a great deal of pleasure and satisfaction to be gained by returning that kindness: by being an active participator in the communities you join, you'll help others overcome the stumbling blocks you once faced and foster connections with others who share your interests. Beyond the community alone, there is something wonderful about using software that you've helped shape; contributing well written bug reports, monetary donations, writing documentation, or testing new releases makes a direct positive impact on the tools you rely on each day. It's one thing to use FLOSS projects for reasons of ethics, privacy, or mere utility, but seeing a page of documentation you've written go live for anyone in the world to learn from, seeing a bug you reported vanish after an update, [a theme you created get added to a game,](https://nathanupchurch.com/blog/new-kmines-themes/) or experiencing your feature request given form in a release *really* draws you in. You're no longer at the mercy of some large tech company who only cares about profit; you're part of a community that cares about people, ideas, and making its software better, more efficient, more usable, and more useful for everyone.
|
||||||
|
|
||||||
|
## The FLOSS mindset
|
||||||
|
|
||||||
|
To distill what I've said above: Things are going to be different, and you may feel disempowered and frustrated for a while until you catch up again. The solution to this, beyond simple patience, is to embrace the fact that by using FLOSS projects, you become a part of the process of making them. Join the community with respect and humility, allow yourself to receive help and kindness from others, and you'll begin to once again remember how it feels to earn your skills. In time, you'll be the one offering help, you'll dance circles around any Windows power-user, and you'll be using tools that you've helped make better. Again I say: welcome. With these small shifts in your thinking, you're going to be in for a good time.
|
BIN
content/blog/that-time-i-drew/1587521755455.jpg
Normal file
BIN
content/blog/that-time-i-drew/1587521755455.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 277 KiB |
BIN
content/blog/that-time-i-drew/1587521817965.jpg
Normal file
BIN
content/blog/that-time-i-drew/1587521817965.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 462 KiB |
BIN
content/blog/that-time-i-drew/1587521888334.jpg
Normal file
BIN
content/blog/that-time-i-drew/1587521888334.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 235 KiB |
37
content/blog/underrated-apps-qownnotes.md
Normal file
37
content/blog/underrated-apps-qownnotes.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: Underrated Apps - QOwnNotes
|
||||||
|
description: QOwnNotes is a terminally underrated native markdown note-taking app, despite the fact that you'll rarely see it mentioned on the internet.
|
||||||
|
synopsis: QOwnNotes is a terminally underrated native markdown note-taking app, despite the fact that you'll rarely see it mentioned on the internet.
|
||||||
|
date: 2023-08-09
|
||||||
|
tags:
|
||||||
|
- FOSS/FLOSS
|
||||||
|
- Underrated Apps
|
||||||
|
imageURL: /img/qownnotes.webp
|
||||||
|
imageAlt: A screenshot of QOwnNotes showing a note subfolder panel beside markdown editor and preview panels.
|
||||||
|
mastodon_id: "110862579682916657"
|
||||||
|
---
|
||||||
|
[](/img/qownnotes.webp)
|
||||||
|
|
||||||
|
## What makes a good note taking app?
|
||||||
|
After its author decided to make future versions closed-source, I found myself searching for a replacement for the excellent [Notable](https://notable.app/). Unfortunately, while note-taking applications are a dime a dozen, they're also common ground for the [Notable](https://notable.app/) / [SimpleNote](https://simplenote.com/) style FLOSS bait-and-switch: developers release an outstanding note-taking app, only to make future versions closed-source, or make it inordinately difficult to properly and fully make use of the software without paying for a hosted service. Victim to the former, I set out to find a note-taking app that I felt could not only be trusted to operate in the spirit of FLOSS philosophy, but also met some key criteria:
|
||||||
|
|
||||||
|
1. Native Code
|
||||||
|
|
||||||
|
Note-taking apps often use Electron. Rather than running another full-fat web-browser (chromium no less) just to *type text*, I wanted something lightweight that would integrate well with the look and feel of the rest of my system.
|
||||||
|
|
||||||
|
2. Notes in an open format
|
||||||
|
|
||||||
|
There are few things worse that note apps that force you to save your notes in their weird proprietary format, or store your notes into a database somewhere that you can't see. If I'm writing text, I want my notes saved into normal text files that I can see, change, delete, and move, without relying on my note-taking app.
|
||||||
|
|
||||||
|
3. Markdown support
|
||||||
|
|
||||||
|
Given that I often work with static site generators, I have come to treasure Markdown. Beyond the minimal interface that markdown editors offer, Markdown keeps distractions low, allowing you to quickly format your notes without being bogged down in office-suite style toolbars and menus full of formatting buttons and dropdowns.
|
||||||
|
|
||||||
|
## Enter QOwnNotes
|
||||||
|
While, tragically, I rarely see this program mentioned in articles or recommended in forums, [QOwnNotes](https://www.qownnotes.org/) is the only note taking app I've managed to find that meets all of the above requirements; written in C++ and QT, it's zippy, light on resources, and integrates well with your desktop, especially if you're using KDE Plasma on GNU/Linux. Notes are stored in plain old markdown files, so you can edit them in any text editor, and markdown support is, of course, excellent. As if this weren't enough to make QOwnNotes a strong contender for a Notable replacement, the app is one of the more flexible note taking programs I've ever seen. It has a plugin system with a wealth of additions written by users, the flexible interface can be customized to suit your workflow precisely, and you can select from many built-in editor themes, or even craft your own. QOwnNotes also features integration with Nextcloud and OwnCloud. Best of all, the project is still actively developed and the developer, [Patrizio Bekerle](https://social.qownnotes.org/@patrizio), is responsive to GitHub issues (and has the patience of a saint!).
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
No solution is perfect. When setting up QOwnNotes for the first time, a page in the set-up wizard will notify you that collection of usage data is enabled by default. While the page also details how to disable data collection, this is a departure from the opt-in approach usually taken by FLOSS projects, and is sure to put a dent in more privacy-minded users' trust in the program. While highly customizable, QOwnNotes is by no means simple to configure. The settings menu is absolutely crammed with options, and default layouts tend to make use of lots of panels and icons. You can absolutely achieve a streamlined interface with QOwnNotes, but don't expect your ideal setup out of the box without some tweaking. Finally, programs like Syncthing can cause issues while editing notes, interfering with QOwnNotes' autosave and causing it to occasionally stop working entirely until the note is closed and re-opened. I can't imagine this would be an issue when using the built-in NextCloud integration, but it's something to look out for if you prefer to sync your notes with something that doesn't require a web-server.
|
||||||
|
|
||||||
|
## In summary
|
||||||
|
For me, QOwnNotes checks all the boxes. I would love to see *opt-in* telemetry, and I do wish that I could simply disable auto-save to avoid conflicts with Syncthing (Please reach out to me if you know how), but I feel that these are minor issues for a native markdown note-taking app. A real diamond in the rough, I cannot overstate how much I appreciate Patrizio's ongoing efforts on QOwnNotes.
|
33
content/blog/up-to-my-eyeballs.md
Normal file
33
content/blog/up-to-my-eyeballs.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Up to My Eyeballs
|
||||||
|
description: What I’ve been up to instead of posting on my blog.
|
||||||
|
date: 2024-02-26
|
||||||
|
tags:
|
||||||
|
- Quick Thoughts
|
||||||
|
synopsis: What I’ve been up to instead of posting on my blog.
|
||||||
|
imageURL: /img/logo_post.svg
|
||||||
|
mastodon_id: "112001639227268153"
|
||||||
|
---
|
||||||
|
If you’re one of the handful of people who read my blog, you may have noticed that I hadn’t posted yet in February. You see, about a month ago, I decided to implement that neat trick some people are doing that allows them to use replies to a mastodon post as comments on a web page. While I *could* have lifted [Juhis’](https://hamatti.org/posts/blog-comments-via-mastodon/) implementation wholesale and been done in a half-hour, naturally, I decided to complicate things and build the feature using web components.
|
||||||
|
|
||||||
|
As if this wasn’t enough, I figured that my site could use a bit of sprucing up. I began with changes to the typography; surely, I thought, this would only represent some minor changes to the CSS! Alas, I came across a link to [utopia.fyi](https://utopia.fyi/) and decided to implement fluid space and type scaling. I found this rather confusing at first as [utopia.fyi](https://utopia.fyi/) is pretty unclear about exactly how this works, but I got it working nicely in the end and I was even able to remove my media query in exchange for one small container query, which it seems [we can safely use now](https://caniuse.com/css-container-queries).
|
||||||
|
|
||||||
|
I also decided that I was sick of how crowded my font folder was, so I replaced the lot with two beautiful variable typefaces that only require one font file each for a slew of styles: [Manrope](https://www.gent.media/manrope) and [Fraunces](https://fraunces.undercase.xyz/).
|
||||||
|
|
||||||
|
To make a long story short: I rendered my website unusable by taking on too much at once. Could I have switched branches and uploaded the odd post? Surely, just like I *could* do my taxes the minute I get my W2, or I *could* get more exercise. In any case, I now have a prettier, leaner website with:
|
||||||
|
* Mastodon comments
|
||||||
|
* Fluid type
|
||||||
|
* Two variable typefaces instead of two giant font families
|
||||||
|
* Zero breakpoints
|
||||||
|
|
||||||
|
...and I’m fairly chuffed. Some things I plan on doing in the future include:
|
||||||
|
|
||||||
|
* Styling for blockquotes, including optical margins
|
||||||
|
* Fixing styling for images that *aren’t* in a `<figure>`
|
||||||
|
* Better styling for code fences
|
||||||
|
* Implementing an image gallery
|
||||||
|
* Creating a /uses page
|
||||||
|
* Doing some of [this stuff](https://jamesg.blog/2024/02/19/personal-website-ideas/), and/or [this stuff](https://shellsharks.com/notes/2023/08/15/website-component-checklist)
|
||||||
|
* Finally getting around to making a professional site for my work accomplishments, et cetera
|
||||||
|
|
||||||
|
It feels good to have a working site once more. I’m very excited about these changes and keen as mince to get posting again!
|
40
content/blog/vegan-and-alternative-diets-in-foodservice.md
Normal file
40
content/blog/vegan-and-alternative-diets-in-foodservice.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
title: ADGs - Alternative Diet Guests and Foodservice
|
||||||
|
description: Breaking down the alternative-diet restaurant experience to offer some perspective and advice to foodservice professionals and proprietors.
|
||||||
|
date: 2023-10-03
|
||||||
|
tags:
|
||||||
|
- Vegan Cooking
|
||||||
|
- Restaurants
|
||||||
|
synopsis: Breaking down the alternative-diet restaurant experience to offer some perspective and advice to foodservice professionals and proprietors.
|
||||||
|
imageURL: /img/k8-sWEpcc0Rm0U-unsplash.webp
|
||||||
|
imageAlt: An overhead view of a restaurant interior showing guests sitting at tables with white tablecloths, eating pastries.
|
||||||
|
mastodon_id: "111173763912666764"
|
||||||
|
---
|
||||||
|
I've been a vegan for close to a decade. From washing dishes, slinging cocktails, and pouring latte art, to recipe development, hiring, compliance, and multi-location operations, I've also been around the block a few times when it comes to foodservice. So when I tell you that the vast majority of foodservice establishments of any stripe are utter and complete complete nightmares for people with alternative diets, I hope you'll take me at my word.
|
||||||
|
|
||||||
|
For much of human civilization, food has represented more than mere fuel. Sharing a meal is a cornerstone of social and family life for billions of us around the globe; we gather together to discuss our day, we do business, we express love, and we wax philosophical over plates, bowls, leaves, platters, and hands holding an incredible variety of foods. In the present day, a growing class of people are becoming separated from the labor of cooking, while continuing to partake in its life-giving fruits. As a result of privilege, or a lack thereof, millions have come to rely on restaurants, bars, coffee shops, food stalls and stands to sustain themselves, and to participate in the social act of eating with others. While the resultant decline in food preparation skills is troubling, it is perhaps equally concerning that, whereas food was once created as a labor of love for those close to us, decisions made in the kitchens that feed so many leave those of us with less common needs and preferences largely excluded from many establishments, and with them, the important social role that they have come to play.
|
||||||
|
|
||||||
|
## For those with alternative diets, restaurants can be a source of stress.
|
||||||
|
|
||||||
|
For me and others like me, visiting a restaurant can be a difficult experience. From front of house staff with no idea what allergens their menu items contain, who don't understand the difference between Coeliac Disease and veganism, or who forget to include important information such as allergies or religious dietary restrictions into orders, to back of house staff who are equally uneducated on alternative dietary needs, lack the flexibility of process or inventory necessary to be accommodating, or who simply don't care and either treat accommodating guests with alternative diets as an encumbrance, or even lie outright and serve food that doesn't meet a guest's philosophical, religious, or even medical requirements, many of us have complicated feelings about going out to eat.
|
||||||
|
|
||||||
|
I once worked with a CEO who often invited the management team to dinner. We went to some of the best regarded restaurants in Chicago, but more often than not, I would wind up with a plate of french fries (dry, as the truffle aioli naturally wasn't vegan) and a glass of whiskey. Sometimes I'd be accommodated with a meal in-earnest. Once, I received an under-seasoned plate of couscous and mushy diced vegetables, and on another occasion, an over-priced bowl of black-bean soup. If there did happen to be a vegan menu item, it would rarely contain any protein, leaving me hungry after the meal. What ought to have been an enjoyable evening with colleagues became a source of anxiety; failing to attend these events may have left me on the sidelines at work, but going along left me feeling excluded and wishing that I were in my home kitchen making something worth eating.
|
||||||
|
|
||||||
|
## We're everywhere, and we probably want to eat at your restaurant.
|
||||||
|
|
||||||
|
Over the years I've heard many restaurateurs, managers, and chefs balk at the idea of creating a vegan menu item, claiming that it would be a moot exercise as they don't receive any vegan custom. The fact is, however, that in almost every instance they had absolutely no idea how many of their guests complied with any sort of alternative diet. From the smallest towns to the largest cities, ADGs are everywhere. If you have any custom at all, you likely have ADGs. Groups such as vegans, vegetarians, those who keep halal or kosher, those with allergies or Coeliac Disease, and so on each comprise large numbers of people, let alone in combination. If any reasonable number of people live or work near your establishment, there are almost certainly people who wish they could eat there, so near to where they spend so much of their time, but cannot for reasons either real or perceived.
|
||||||
|
|
||||||
|
Vegans and others with alternative diets have years of bad experiences to hone our expectations; we've each mentally composed a vast list of red flags - telltale signs that an establishment is going to be more trouble than its worth. When seeing these red flags, we are likely to anticipate either outright hostility, ranging from a bad attitude to being deliberately served foods we cannot eat, a lack of knowledge from staff resulting in difficulty ordering and a low degree of trust that our meal will be safe to eat, or to simply be told that there are no options available. In these situations, we may either simply avoid an establishment, or if dragged in by friends or colleagues, stick to what we know is safe: a black coffee, a fruit bowl, or even some fries, sans aioli, and a glass of whiskey. In all likelihood, a number of tickets for $5 coffees at your establishment might have been tickets for a starter and an entrée had an ADG seen one or two suitable menu items, or even just felt as though they had a fair chance at being accommodated.
|
||||||
|
|
||||||
|
## ADG items don't just make your menu accessible to swathes of new customers, done well, they should also excite your regulars.
|
||||||
|
|
||||||
|
Besides being reductive, the assertion that it's pointless to implement a vegan menu item because the business simply isn't there also implies that ADG items will only be saleable to ADGs. I can't count the number of times I've been told by someone that they simply cannot tolerate vegan food; each time, I'm tempted to express my condolences for my conversation partner's inability to enjoy a baguette, fresh fruit, vegetables, romesco sauce, peanut butter, french fries, Oreos, Swedish Fish candy, hummus, baba ganouj, nuts, Ritz Crackers, ketchup, good dark chocolate, bagels, pretzels, most potato chips, microwave popcorn, and dried pasta, foccaccia, olives, et cetera. While vegan food may be perceived by some as a special category of food, foreign and strange, only to be consumed by yet stranger people in acts of corporal mortification, vegan food, and indeed food suitable for most ADGs, is all around us; most of us already enjoy it every day, in one way or another.
|
||||||
|
|
||||||
|
[.")](/img/banana-nut-doughnut.webp)
|
||||||
|
|
||||||
|
|
||||||
|
Not only is there nothing strange about the food, there is also nothing so terribly different about the people who eat it. ADGs expect all of the joy from food that anyone else does: a balance of salt, fat, and acid, contrasting textures, mouth-watering flavors and aromas, and protein to keep us feeling full and satisfied. Any dish that meets these expectations ought to be worth ordering for *any* guest. This is a tough point for many otherwise proud chefs and proprietors to swallow; all else being equal, the fact is that most competent ADG menu items should be seeing sales in line with other items. Further, ADG menu items can make a menu accessible to multiple groups at once; a vegan item is also accessible to Catholics during Lent, in most instances those who keep halal or kosher, people with red meat, egg, or dairy allergies, vegetarians, and pescetarians.
|
||||||
|
|
||||||
|
## Making good food is just the start.
|
||||||
|
|
||||||
|
Designing and executing a competent ADG item or two is a fine start, but it's just that. Establishing trust takes time and effort; there are operational and cultural shifts that need to take place before a restaurant can be considered truly accessible to ADGs. While that work takes place over time, beginning with one or two competent menu items is a low-risk way for most independent establishments and other foodservice SMEs to expand their customer base. Bodies and beliefs are infinitely varied; likewise, people have an inexhaustible number of reasons to choose an ADG accessible food. As the role of restaurants becomes less of an occasional treat and more of a daily necessity, investing in menu development with ADG accessibility as a goal is increasingly becoming a simple matter of good business sense.
|
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: "Visiting Chicago Incense Maker Dave of The World Makes Scents"
|
||||||
|
description: "I had the pleasure of meeting Dave at The World Makes Scents studio in Bridgeport, Chicago."
|
||||||
|
date: 2024-08-04
|
||||||
|
tags:
|
||||||
|
- Incense
|
||||||
|
- Chicago
|
||||||
|
synopsis: "I had the pleasure of meeting Dave at The World Makes Scents studio in Bridgeport, Chicago."
|
||||||
|
imageURL: /img/pexels-harris-rigorad-478484242-25261413.webp
|
||||||
|
imageAlt: The Chicago flag.
|
||||||
|
mastodon_id: "112906515155787958"
|
||||||
|
---
|
||||||
|
Whether due to trauma inflicted by gas-station sticks laden with synthetic patchouli oil or the stigma unfairly landed upon the fragrant sticks by association with generations of teenagers seeking to obscure the olfactory remnants of their smoky intemperance, in the part of the world that I currently occupy, incense remains a niche interest — not only with respect to those who enjoy it; fewer yet ever attempt the art of incense-making. Western incense makers seem to huddle together into whatever dusty corner of the internet they can occupy, often knowing each other by handle and legal name alike, where together they scratch and scrape into one small pile whatever little crumbs of knowledge regarding this ancient craft are to be had in languages written with Latin-script alphabets. You can imagine, then, how thrilled I was to be invited by Dave of [The World Makes Scents](https://theworldmakesscents.com/) to see the studio that he shares with his wife, Raksmey.
|
||||||
|
|
||||||
|
The workshop is situated in an arts complex: part gallery, and part studio and event space. The walls are lined with pieces from the artists who let space there; as your footsteps echo across the lacquered wooden floors, you can't help but allow your attention to be arrested every few steps by some piece or another. In particular, I recall several striking pieces not far from The World Makes Scents' small studio featuring large nude figures at the fore of a shallow depth of field, a lenticular effect making the images appear to shift and change as you walk by them.
|
||||||
|
|
||||||
|
When we reached the studio a stick from a recent test batch was burning; coated with coarsely ground orange zest, it emitted an impressively clear and sweet note of orange oil. Dave told me the story of how Raksmey learned to make incense from women at a Buddhist temple near her home in Cambodia, how the two met, traveled through Vietnam together, rescued their pet dog from perilous circumstances, and disembarked from a plane in the U.S. right as the pandemic was entering the American collective consciousness. It's not my story to tell, so I'll leave it there, but the tale of how the pair came to be making such lovely incense in Chicago's Bridgeport neighborhood is nothing short of miraculous, and likely the most compelling pandemic hobby success story I've heard.
|
||||||
|
|
||||||
|
Since first sampling their incense through [the incense exchange subreddit,](https://www.reddit.com/r/IncenseExchange/) I had been impressed with how clean the fragrances were from their cones. I had chalked this up to the cones' golf-tee shape, which keeps the ember from becoming too large, and consequently, the burn temperature from becoming too high; seeing how the materials were processed, however, showed me how much more there was to the story. As if sourcing high quality fragrant materials wasn't enough of a challenge, processing them when they arrive is fraught.
|
||||||
|
|
||||||
|
Incense materials should, ideally, be reduced down to a particle size of 100 microns or less. The fastest methods of grinding introduce heat, which breaks down fragrant oils, and renders resins sticky and impossible to work with. Most small incense makers throughout history have thus resorted to processes such as stone grinding or the even more agonizingly slow filing of fragrant woods. Quietly humming in the background was Dave's solution to this conundrum: an array of ball mills, like giant rock-tumblers designed to carefully mill heat-sensitive materials such as black powder for firearms and pyrotechnics; the machines each consist of a rubber-lined drum atop a pair of motorized rollers that turns the drum continuously. The material to be ground is loaded into the machine alongside a series of stainless-steel balls that collide with material inside the spinning drum to break down everything from dried rose petals to cedar wood into a stunningly fine powder. The process takes hours, sometimes days, but at least it's hands-off. Following this, the ground plant matter is run through a series of increasingly fine sieves stacked atop a machine that shakes them so violently that it will soon be bolted to the floor of the studio. Suitably fine material will be used to produce incense; the rest will undergo the process once more.
|
||||||
|
|
||||||
|
The results of this procedure are striking; among the samples I left with was a bag of ground patchouli leaf, the texture of talcum powder, so redolent with its natural oils and aromatics that it almost smelled of fresh mint and myrrh resin. Once ground, ingredients are blended together with a small amount of binder; water is then added to form a dough. If destined to become cones, the dough is loaded into a sort of caulking-gun, which is used to extrude a long sausage which will be cut at regular intervals and shaped by hand into those signature golf-tee cones. To make sticks, the dough is packed into a large hand-cranked extrusion machine, capable of extruding some eight to ten noodles of incense at a time which are collected onto a wooden board before being straightened and transferred to a screen for drying. When the cones or sticks have dried completely, they are packaged by hand, labeled, and sent out to incense appreciators world-over.
|
||||||
|
|
||||||
|
Of course, the recipes that result in various fragrant doughs to extrude don't spring from nowhere; research and development is an area for which Dave clearly has a great interest and affinity. A whirlwind in the workshop, he produced innumerable ingredients, from wormwood to various frankincense varieties, sprinkling them atop a burning coal to give an impression of the fragrance of each as it burns. The small space housed many drawers and shelves of powders, tinctures, and oils, experiments both successful and otherwise. Vanilla was present in every form and variety as a part of the development process for an upcoming product, from simple extracts to pastes and a high end powdered variety that smelled rich, complex, and tobacco-like. Boxes of incense samples were produced from makers across the globe, from independent makers to large incense houses; all styles were represented, from Tibetan rope incense to bakhoor.
|
||||||
|
|
||||||
|
Very much in-line with the brand's emphasis on transparency, Dave's openness regarding processes and ingredients was impressive in an industry where players keep secrets close to their chests. Further, scaling up incense making into a viable business is no mean feat when you're not willing to compromise on quality. Even the famed Singapore incense maker [Kyara Zen wrote](https://www.kyarazen.com/incense-stick-making-a-walk-through/) of this difficulty:
|
||||||
|
|
||||||
|
> The truth is, I’ve not been able to scale up production as everything’s still very much purely hand made, from converting the raw material into powder, to the ingredient blending, to the extrusion, drying, collection etc.
|
||||||
|
|
||||||
|
Outside of large incense houses that use industrial hammer mills to break down whole aromatics, it's a painstaking endeavor to produce even small amounts of incense for personal enjoyment, let alone managing to increase output and reduce labor time such that it's viable to sell the incense you make. There is a reason quick and easy to make "hand dipped" incense is seen so often in stores despite its clear inferiority to its traditionally made, non-synthetic counterpart. What Dave and Raksmey have achieved in such a short time is nothing short of remarkable.
|
||||||
|
|
||||||
|
I'm very grateful to Dave for the invitation, for showing me the studio, offering a fascinating insight into The World Makes Scents' incense production methods, and for all of the incredible samples and goodies I left with. It's a wonderful thing to be able to meet with another incense maker and share knowledge and enthusiasm over the topic, and hopefully we'll be able to meet again soon; I'll no doubt have some samples to share myself when that time arrives!
|
@ -1,3 +1,3 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
eleventyExcludeFromCollections: true
|
eleventyExcludeFromCollections: true
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,17 @@ permalink: /feed/feed.xml
|
|||||||
</author>
|
</author>
|
||||||
{%- for post in collections.posts | reverse %}
|
{%- for post in collections.posts | reverse %}
|
||||||
{% set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset %}
|
{% set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset %}
|
||||||
{% if post.image-url %}{% set imageURL %}{{ post.image-url | htmlBaseUrl(metadata.url) }}{% endset %}{% endif %}
|
{% if post.data.imageURL %}{% set imageURL %}{{ post.data.imageURL | htmlBaseUrl(metadata.url) }}{% endset %}{% endif %}
|
||||||
{% set defaultImageURL %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endset %}
|
{% set defaultImageURL %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endset %}
|
||||||
<entry>
|
<entry>
|
||||||
<title>{{ post.data.title }}</title>
|
<title>{{ post.data.title }}</title>
|
||||||
|
<description>{{ post.data.description | truncate(150) }}</description>
|
||||||
<link href="{{ absolutePostUrl }}"/>
|
<link href="{{ absolutePostUrl }}"/>
|
||||||
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
||||||
<id>{{ absolutePostUrl }}</id>
|
<id>{{ absolutePostUrl }}</id>
|
||||||
<image>
|
<image>
|
||||||
<url>{% if post.image-url %}{{ imageURL }}{% else %}{{ defaultImageURL }}{% endif %}</url>
|
<url>{% if post.data.imageURL %}{{ imageURL }}{% else %}{{ defaultImageURL }}{% endif %}</url>
|
||||||
<title>{% if post.image-alt %}{{ post.image-alt }}{% else %}{{ metadata.defaultPostImageAlt }}{% endif %}</title>
|
<title>{% if post.data.imageAlt %}{{ post.data.imageAlt }}{% else %}{{ metadata.defaultPostImageAlt }}{% endif %}</title>
|
||||||
<link href="{{ absolutePostUrl }}"/>
|
<link href="{{ absolutePostUrl }}"/>
|
||||||
</image>
|
</image>
|
||||||
<content type="html">{{ post.templateContent | transformWithHtmlBase(absolutePostUrl, post.url) }}</content>
|
<content type="html">{{ post.templateContent | transformWithHtmlBase(absolutePostUrl, post.url) }}</content>
|
||||||
|
47
content/feeds.njk
Normal file
47
content/feeds.njk
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
pagination:
|
||||||
|
data: collections
|
||||||
|
size: 1
|
||||||
|
alias: tag
|
||||||
|
filter:
|
||||||
|
- all
|
||||||
|
- post
|
||||||
|
- posts
|
||||||
|
- tagList
|
||||||
|
addAllPagesToCollections: true
|
||||||
|
eleventyComputed:
|
||||||
|
title: “{{ tag }}”
|
||||||
|
permalink: "/feeds/{{ tag | slugify }}.xml"
|
||||||
|
---
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>{% set postslist = collections[ tag ] %}
|
||||||
|
<?xml-stylesheet href="../xsl/basic.xsl" type="text/xsl"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{ metadata.language }}">
|
||||||
|
<title>{{ title }} from {{ metadata.title }}</title>
|
||||||
|
<subtitle>{{ tag }}: {{ metadata.description }}</subtitle>
|
||||||
|
<link href="{{ metadata.url }}feeds/{{ tag | slugify }}.xml" rel="self"/>
|
||||||
|
<link href="{{ metadata.url | addPathPrefixToFullUrl }}"/>
|
||||||
|
<updated>{{ postslist | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
|
||||||
|
<id>{{ metadata.url }}</id>
|
||||||
|
<author>
|
||||||
|
<name>{{ metadata.author.name }}</name>
|
||||||
|
<email>{{ metadata.author.email }}</email>
|
||||||
|
</author>
|
||||||
|
{%- for post in postslist | reverse %}
|
||||||
|
{% set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset %}
|
||||||
|
{% if post.data.imageURL %}{% set imageURL %}{{ post.data.imageURL | htmlBaseUrl(metadata.url) }}{% endset %}{% endif %}
|
||||||
|
{% set defaultImageURL %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endset %}
|
||||||
|
<entry>
|
||||||
|
<title>{{ post.data.title }}</title>
|
||||||
|
<description>{{ post.data.description | truncate(150) }}</description>
|
||||||
|
<link href="{{ absolutePostUrl }}"/>
|
||||||
|
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
||||||
|
<id>{{ absolutePostUrl }}</id>
|
||||||
|
<image>
|
||||||
|
<url>{% if post.data.imageURL %}{{ imageURL }}{% else %}{{ defaultImageURL }}{% endif %}</url>
|
||||||
|
<title>{% if post.data.imageAlt %}{{ post.data.imageAlt }}{% else %}{{ metadata.defaultPostImageAlt }}{% endif %}</title>
|
||||||
|
<link href="{{ absolutePostUrl }}"/>
|
||||||
|
</image>
|
||||||
|
<content type="html">{{ post.templateContent | transformWithHtmlBase(absolutePostUrl, post.url) }}</content>
|
||||||
|
</entry>
|
||||||
|
{%- endfor %}
|
||||||
|
</feed>
|
@ -3,24 +3,36 @@ layout: layouts/home.njk
|
|||||||
eleventyNavigation:
|
eleventyNavigation:
|
||||||
key: Home
|
key: Home
|
||||||
order: 1
|
order: 1
|
||||||
numberOfLatestPostsToShow: 10
|
numberOfLatestPostsToShow: 5
|
||||||
|
numberOfNowPostsToShow: 1
|
||||||
---
|
---
|
||||||
|
<div class="now">
|
||||||
|
<h2>What I've been up to:</h2>
|
||||||
|
{% set postsCount = collections.now | length %}
|
||||||
|
{% set latestPostsCount = postsCount | min(numberOfNowPostsToShow) %}
|
||||||
|
{% set postslist = collections.now | head(-1 * numberOfNowPostsToShow) %}
|
||||||
|
{% set postslistCounter = postsCount %}
|
||||||
|
{% set showPostListHeader = false %}
|
||||||
|
{% include "postslist.njk" %}
|
||||||
|
<a class="link-button" href="/now/">
|
||||||
|
<button type="button">
|
||||||
|
See more on the “now” page
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% set postsCount = collections.posts | length %}
|
{% set postsCount = collections.posts | length %}
|
||||||
{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %}
|
{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %}
|
||||||
{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %}
|
{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %}
|
||||||
{% set postslistCounter = postsCount %}
|
{% set postslistCounter = postsCount %}
|
||||||
|
{% set showPostListHeader = true %}
|
||||||
{% include "postslist.njk" %}
|
{% include "postslist.njk" %}
|
||||||
|
|
||||||
{% set morePosts = postsCount - numberOfLatestPostsToShow %}
|
{% set morePosts = postsCount - numberOfLatestPostsToShow %}
|
||||||
{% if morePosts > 0 %}
|
{% if morePosts > 0 %}
|
||||||
<p>See {{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} in <a href="/blog/">the blog</a>.</p>
|
<a class="link-button" href="/blog/">
|
||||||
|
<button type="button">
|
||||||
|
See {{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} in the blog
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# List every content page in the project #}
|
|
||||||
{#
|
|
||||||
<ul>
|
|
||||||
{%- for entry in collections.all %}
|
|
||||||
<li><a href="{{ entry.url }}"><code>{{ entry.url }}</code></a></li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ul>
|
|
||||||
#}
|
|
||||||
|
22
content/now.njk
Normal file
22
content/now.njk
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
eleventyNavigation:
|
||||||
|
key: Now
|
||||||
|
order: 4
|
||||||
|
---
|
||||||
|
<article class="post">
|
||||||
|
<h1>Now: What’s Been Going on Lately?</h1>
|
||||||
|
{% set now = collections.now | last %}
|
||||||
|
<h2>{{ now.data.title }}</h2>
|
||||||
|
{{ now.content | safe }}
|
||||||
|
</article>
|
||||||
|
<p class="metadata">Updated: {{ now.date | readableDate }} | <a href="https://nownownow.com/about">What is a now page?</a></p>
|
||||||
|
|
||||||
|
{% set postsCount = collections.now | removeMostRecent | length %}
|
||||||
|
{% if postsCount > 1 %}
|
||||||
|
<h2>Previous Entries:</h2>
|
||||||
|
{% set postslist = collections.now | removeMostRecent %}
|
||||||
|
{% set showPostListHeader = false %}
|
||||||
|
{% include "postslist.njk" %}
|
||||||
|
{% endif %}
|
||||||
|
|
6
content/now/now.11tydata.js
Normal file
6
content/now/now.11tydata.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
tags: [
|
||||||
|
"now"
|
||||||
|
],
|
||||||
|
"layout": "layouts/post.njk",
|
||||||
|
};
|
13
content/now/now_2024-01-08.md
Normal file
13
content/now/now_2024-01-08.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: Fragrance, Making Things Last, Rust, and more.
|
||||||
|
description: Tentatively making incense sticks, reducing, reusing, and recycling, and doing my best to learn Rust.
|
||||||
|
synopsis: Tentatively making incense sticks, reducing, reusing, and recycling, and doing my best to learn Rust.
|
||||||
|
date: 2024-01-08
|
||||||
|
imageURL: /img/pexels-pixabay-262484_copy.avif
|
||||||
|
imageAlt: A black Braun watch with white and yellow hands on a dark background.
|
||||||
|
---
|
||||||
|
Lately, I've gotten back into the swing of making Japanese style incense - a nerve-wracking endeavour because you never quite know how the expensive aromatics you've spent hours grinding, kneading, and extruding will turn out when you light a stick. Will my latest batch smell like burning tires when it's properly cured? Tune in next time to find out.
|
||||||
|
|
||||||
|
I've also been on a mission to reduce what I consume by focusing on buying well once, and buying reusable items wherever possible. Starting with one good fountain pen, I've now got a number of cotton handkerchiefs and some reusable cotton "paper towels."
|
||||||
|
|
||||||
|
Rust has been on the top of my mind, and I've been going through a Rust course on Udemy. Coming from JavaScript, it's a lot! I desperately want to begin writing native applications for GNU/Linux using Rust and Qt, so I'm determined to keep going even if using strings is an order of magnitude more complicated than `const myString = 'Hello there.'`.
|
15
content/now/now_2024-04-13.md
Normal file
15
content/now/now_2024-04-13.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: Touching Grass
|
||||||
|
description: What I've been up to lately — fresh air, more incense making, and doing more KDE stuff.
|
||||||
|
synopsis: I go outside, make more incense, contribute to KDE, and eat brownies.
|
||||||
|
date: 2024-04-13
|
||||||
|
imageURL: /img/justin-veenema-gf03H-I7C9Y-unsplash_copy.webp
|
||||||
|
imageAlt: A blue neon sign that reads "Time is precious."
|
||||||
|
---
|
||||||
|
For some reason, I just seem to be itching to get outside lately. Perhaps I'm coming out of some sort of hibernation — after all, spring has sprung, the ornamental pear trees are [somewhat unfortunately](https://www.vice.com/en/article/7x4zza/heres-why-the-trees-on-your-street-smell-like-semen) in full bloom, turtles are basking in the sunshine on riverside logs, and I just don't feel right if I haven't fed a duck some frozen peas in a few days. I've been making heavy use of my under-desk treadmill too; walking a few miles while checking my feeds and answering emails is a surprisingly nice thing to do in the morning after breakfast.
|
||||||
|
|
||||||
|
But lest my doctor fall and injure himself from shock, I've also been engaging in plenty of activities that *don't* involve sunshine, exercise, and fresh air. Just this morning I broke a freshly-dried batch of incense down into five inch pieces. This time I modified a previously successful build to feature rose and myrrh. While there are many varieties of myrrh, the variety I used predominantly (Commiphora Kua) is pungent, bitter, perfumey, and dignified. I wish I could describe it better, but it's quite a difficult fragrance to relate to much else. It really sings in a blend, and I thought it would play nicely with the classic combination of rose and agarwood. The sticks extruded extremely well, and they burn nicely after one night of drying. I'm going to come back to them in two weeks to see how the fragrance has turned out after they've had time to cure and settle.
|
||||||
|
|
||||||
|
I've started to muck-in a bit more with KDE which, if you don't know, is an international community that creates free and open source software. Since [my first contribution](../../blog/new-kmines-themes/) (barring bug reports) several months ago, I've been more active on the KDE forum, participating in the visual design group on Matrix and joining in conversations on KDE's GitLab when design issues arise. I may not know C++, but I have been designing things professionally for over a decade; as a huge fan of KDE and a daily user of their Plasma desktop and many KDE applications, I'm delighted to be helping. I'd also like to point out that KDE is doing some really exciting work; KDE is recommending their [Kirigami framework](https://develop.kde.org/frameworks/kirigami/) for all new KDE applications. Kirigami is a *convergent* UI framework, a framework that allows developers to easily create "beautiful apps that run on computers, phones, TVs and everything in between." KDE also has teams working on [Plasma Bigscreen,](https://plasma-bigscreen.org/) a version of the Plasma desktop for TVs, and [Plasma Mobile,](https://plasma-mobile.org/) for smartphones. *Who else* is doing this? Not only does KDE provide desktop environments for TVs, personal computers, and phones — they've created a framework that allows developers to create applications that work on all of these platforms, without making separate versions. The very same application that works on your desktop can run on your phone. How exciting!
|
||||||
|
|
||||||
|
To complete this update, my partner has recently gotten back into baking brownies, so over the past few weeks I've exercised the solemn duty of ensuring that not the least morsel of the several batches they've baked goes to waste, whether they're hazelnut and chocolate chip, walnut, or cookie butter. It's a hard life, this one. Perhaps this might go some way towards an explanation as to where all of my extra energy has come from of late.
|
13
content/now/now_2024_02_26.md
Normal file
13
content/now/now_2024_02_26.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: Not Blogging, Incense, and Scientific Naps
|
||||||
|
description: When you hear what I’ve been up to, it may just put you to sleep.
|
||||||
|
synopsis: When you hear what I’ve been up to, it may just put you to sleep.
|
||||||
|
date: 2024-02-26
|
||||||
|
imageURL: /img/pexels-pixabay-277458_copy.webp
|
||||||
|
imageAlt: A series of clocks on tall black poles in a clearing in front of some trees.
|
||||||
|
---
|
||||||
|
I spent about a month working on my blog, while [not blogging on the thing](https://nathanupchurch.com/blog/up-to-my-eyeballs/), but I’m very pleased about the new changes. The biggest new feature as a result of the work is the ability to comment on my posts via Mastodon!
|
||||||
|
|
||||||
|
After a failed batch, I went back to basics and made some plain Palo Santo incense that turned out very, very well. I expect I’ll write a post about it soon. I also caved and got three varieties from [Yi-Xin Craft Incense](https://craft-incense.com/pages/about-the-artist)’s February collection. Not only is his stuff consistently gorgeous, it’s also great reference incense for those masochists who like to make their own. If you want to know what a *good* stick can be, you can’t go wrong with one of Ken’s.
|
||||||
|
|
||||||
|
I had a bizarre first experience the other day. In aid of determining whether there is a medical explanation behind my snoring, or whether my unconscious mind simply enjoys keeping my partner up all night, I went for a sleep study. While it’s a fascinating experience, I can’t say I enjoyed it. Sleeping in front of a camera with approximately one million electrodes attached to you overnight is bad enough, let alone spending the next day taking on-camera naps every two hours until five PM! 0/10: wouldn’t do again.
|
@ -9,7 +9,7 @@ Click on a tag to see all posts on the topic.
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul class="taglist">
|
<ul class="taglist">
|
||||||
{% for tag in collections.all | getAllTags | filterTagList %}
|
{% for tag in collections.all | getAllTags | filterTagList | sort(false, false, tag) %}
|
||||||
{% set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
|
{% set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
|
||||||
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a></li>
|
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -12,13 +12,37 @@ pagination:
|
|||||||
layout: layouts/base.njk
|
layout: layouts/base.njk
|
||||||
eleventyComputed:
|
eleventyComputed:
|
||||||
title: Tagged “{{ tag }}”
|
title: Tagged “{{ tag }}”
|
||||||
permalink: /tags/{{ tag | slugify }}/
|
permalink: "/tags/{{ tag | slugify }}/"
|
||||||
---
|
---
|
||||||
<h1>More posts about “{{ tag }}.”</h1>
|
<h1>More posts tagged “{{ tag }}.”
|
||||||
|
<a href="/feeds/{{ tag | slugify }}.xml">
|
||||||
<p class="page-block nodropcap">Here's everything I've posted about {{ tag }}:</p>
|
<!-- RSS Logo -->
|
||||||
|
<svg class="tag-feed-icon" viewBox="0 0 155 155" width="153.349" height="152.909" version="1.0" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>RSS feed for posts tagged {{tag}}.</title>
|
||||||
|
<g transform="translate(-427.323 -373.814)">
|
||||||
|
<ellipse
|
||||||
|
style="opacity:1;fill-opacity:1;fill-rule:nonzero;"
|
||||||
|
transform="matrix(.86996 0 0 .86996 135.156 330.529)"
|
||||||
|
cx="360.357" cy="200.643" rx="24.643" ry="23.929"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill-opacity:1;fill-rule:evenodd;"
|
||||||
|
d="m427.835 455.057-.073-30.273c64.706 3.375 100.619 49.673 101.5 101.94h-30.318c-.503-45.942-31.74-69.996-71.11-71.667z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style="fill-opacity:1;fill-rule:evenodd;"
|
||||||
|
d="m428.201 404.571-.878-30.757C526.75 378.43 580 450.582 580.67 526.724l-31.197-.44c1.365-48.704-34.665-120.267-121.273-121.713Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<p class="page-block nodropcap">Here’s everything I’ve posted with the tag “{{ tag }}:”</p>
|
||||||
|
|
||||||
{% set postslist = collections[ tag ] %}
|
{% set postslist = collections[ tag ] %}
|
||||||
{% include "postslist.njk" %}
|
{% include "postslist.njk" %}
|
||||||
|
<a class="link-button" href="/tags/">
|
||||||
<p class="nodropcap">See <a href="/tags/">all tags</a>.</p>
|
<button type="button">
|
||||||
|
See all tags
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
@ -1,24 +1,53 @@
|
|||||||
const { DateTime } = require("luxon");
|
import { DateTime } from "luxon";
|
||||||
const markdownIt = require("markdown-it");
|
import { eleventyImageTransformPlugin } from "@11ty/eleventy-img";
|
||||||
const markdownItFootnote = require("markdown-it-footnote");
|
import markdownIt from "markdown-it";
|
||||||
const markdownItAnchor = require("markdown-it-anchor");
|
import markdownItFootnote from "markdown-it-footnote";
|
||||||
const mdfigcaption = require('markdown-it-image-figures');
|
import markdownItAnchor from "markdown-it-anchor";
|
||||||
|
import mdfigcaption from 'markdown-it-image-figures';
|
||||||
const pluginRss = require("@11ty/eleventy-plugin-rss");
|
import pluginRss from "@11ty/eleventy-plugin-rss";
|
||||||
const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
|
import pluginSyntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
|
||||||
const pluginBundle = require("@11ty/eleventy-plugin-bundle");
|
import pluginBundle from "@11ty/eleventy-plugin-bundle";
|
||||||
const pluginNavigation = require("@11ty/eleventy-navigation");
|
import pluginNavigation from "@11ty/eleventy-navigation";
|
||||||
const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
|
import prettier from "prettier";
|
||||||
|
import { EleventyHtmlBasePlugin } from "@11ty/eleventy";
|
||||||
|
import {dateSuffixAdder, monthMap, timeFormatter} from "./public/js/modules/mastodonDateTools.js";
|
||||||
|
|
||||||
const figoptions = {
|
const figoptions = {
|
||||||
figcaption: true
|
figcaption: true
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function(eleventyConfig) {
|
export default async function(eleventyConfig) {
|
||||||
|
|
||||||
|
// Helper Functions
|
||||||
|
const multiReplace = (text, replacementTable) => {
|
||||||
|
let newText = text;
|
||||||
|
replacementTable.forEach(x => { newText = newText.replace(x[0], x[1]) });
|
||||||
|
return newText;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Transforms
|
||||||
|
eleventyConfig.addTransform("prettier", function (content, outputPath) {
|
||||||
|
if (outputPath && outputPath.endsWith(".html")) {
|
||||||
|
return prettier.format(content, {parser: "html", bracketSameLine: true, vueIndentScriptAndStyle: true, singleAttributePerLine: true, htmlWhitespaceSensitivity: "ignore"});
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
eleventyConfig.addWatchTarget("content/**/*.{svg,webp,png,jpeg}");
|
eleventyConfig.addWatchTarget("content/**/*.{svg,webp,png,jpeg}");
|
||||||
|
|
||||||
// Official plugins
|
// Official plugins
|
||||||
|
/*
|
||||||
|
eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
|
||||||
|
extensions: "html",
|
||||||
|
formats: ["webp"],
|
||||||
|
widths: [270,540,810,1080],
|
||||||
|
defaultAttributes: {
|
||||||
|
loading: "lazy",
|
||||||
|
decoding: "async",
|
||||||
|
},
|
||||||
|
urlPath: "/img/",
|
||||||
|
});*/
|
||||||
eleventyConfig.addPlugin(pluginRss);
|
eleventyConfig.addPlugin(pluginRss);
|
||||||
eleventyConfig.addPlugin(pluginSyntaxHighlight, {
|
eleventyConfig.addPlugin(pluginSyntaxHighlight, {
|
||||||
preAttributes: { tabindex: 0 }
|
preAttributes: { tabindex: 0 }
|
||||||
@ -38,9 +67,75 @@ module.exports = function(eleventyConfig) {
|
|||||||
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd');
|
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addFilter("removeMostRecent", arr => {
|
||||||
|
return arr.slice(0, arr.length-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shortcodes
|
||||||
|
|
||||||
|
// Cowsay
|
||||||
|
eleventyConfig.addNunjucksFilter("cowsay", cowText => {
|
||||||
|
const cowCaptionReplacementTable = [
|
||||||
|
[`
|
||||||
|
o ^__^
|
||||||
|
o (oo)\\_______
|
||||||
|
(__)\\ )\\/\\
|
||||||
|
||----w |
|
||||||
|
|| ||`, ''],
|
||||||
|
[/\(\s+/g, ''],
|
||||||
|
[/\s+\(/g, ''],
|
||||||
|
[/_{3,}/g, ''],
|
||||||
|
[/-{3,}/g, ''],
|
||||||
|
[/\s\)/g, ''],
|
||||||
|
[/\n/g, ''],
|
||||||
|
[/\s{2,}/g, ' '],
|
||||||
|
[/^ /, '']
|
||||||
|
];
|
||||||
|
|
||||||
|
return `
|
||||||
|
<figure>
|
||||||
|
<pre class="language-" role="img" aria-label="ASCII COW">${cowText}</pre>
|
||||||
|
<figcaption id="cow-caption">
|
||||||
|
A cow thinking: <em>${multiReplace(cowText, cowCaptionReplacementTable)}</em>. The cow is illustrated using
|
||||||
|
preformatted text characters.
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Embed a toot
|
||||||
|
eleventyConfig.addAsyncShortcode("toot", async function(instance, ID) {
|
||||||
|
const tootData = await fetch(
|
||||||
|
`https://${instance}/api/v1/statuses/${ID}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const toot = await tootData.json();
|
||||||
|
|
||||||
|
const dateObj = new Date(toot.created_at);
|
||||||
|
|
||||||
|
const dateTime = `${dateObj.getDate()}${dateSuffixAdder(dateObj.getDate())} of ${monthMap[dateObj.getMonth()]}, ${dateObj.getFullYear()}, at ${timeFormatter(dateObj.getHours(), dateObj.getMinutes())}`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<wc-toot
|
||||||
|
author_name="${toot.account.display_name}"
|
||||||
|
author_url="${toot.url.replace(/\/[0-9]+/, "")}"
|
||||||
|
author_username="${toot.account.username}"
|
||||||
|
avatar_url="${toot.account.avatar_static}"
|
||||||
|
toot_content="${toot.content}"
|
||||||
|
toot_url="${toot.url}"
|
||||||
|
publish_date="${dateTime}"
|
||||||
|
sharp_corner="">
|
||||||
|
</wc-toot>
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
// Passthrough
|
// Passthrough
|
||||||
eleventyConfig.addPassthroughCopy({ 'public/xsl/*': "/xsl/" });
|
eleventyConfig.addPassthroughCopy({ 'public/xsl/*': "/xsl/" });
|
||||||
eleventyConfig.addPassthroughCopy({ 'public/img/*': "/img/" });
|
eleventyConfig.addPassthroughCopy({ 'public/img/*': "/img/" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ 'public/robots.txt': "/" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ 'public/js/*': "/js/" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ 'public/js/webComponents/*': "/js/webComponents" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ 'public/js/modules/*': "/js/modules" });
|
||||||
// Copying so that basic.xsl can use it
|
// Copying so that basic.xsl can use it
|
||||||
eleventyConfig.addPassthroughCopy({ 'public/css/index.css': "/css/index.css" });
|
eleventyConfig.addPassthroughCopy({ 'public/css/index.css': "/css/index.css" });
|
||||||
eleventyConfig.addPassthroughCopy({ 'public/css/webfonts/*': "/css/webfonts/" });
|
eleventyConfig.addPassthroughCopy({ 'public/css/webfonts/*': "/css/webfonts/" });
|
||||||
@ -76,25 +171,31 @@ module.exports = function(eleventyConfig) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Customize Markdown library settings:
|
// Customize Markdown library settings:
|
||||||
|
let markdownItOptions = {
|
||||||
|
html: true,
|
||||||
|
typographer: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let mdLib = markdownIt(markdownItOptions);
|
||||||
|
|
||||||
eleventyConfig.amendLibrary("md", mdLib => {
|
eleventyConfig.amendLibrary("md", mdLib => {
|
||||||
mdLib.use(markdownItAnchor, {
|
mdLib
|
||||||
|
.use(markdownItAnchor, {
|
||||||
permalink: markdownItAnchor.permalink.ariaHidden({
|
permalink: markdownItAnchor.permalink.ariaHidden({
|
||||||
placement: "after",
|
placement: "after",
|
||||||
class: "header-anchor",
|
class: "header-anchor",
|
||||||
symbol: "#",
|
symbol: "#",
|
||||||
ariaHidden: false, // Features to make your build faster (when you need them)
|
ariaHidden: false
|
||||||
|
|
||||||
// If your passthrough copy gets heavy and cumbersome, add this line
|
|
||||||
// to emulate the file copy on the dev server. Learn more:
|
|
||||||
// https://www.11ty.dev/docs/copy/#emulate-passthrough-copy-during-serve
|
|
||||||
|
|
||||||
// eleventyConfig.setServerPassthroughCopyBehavior("passthrough");
|
|
||||||
}),
|
}),
|
||||||
level: [1, 2, 3, 4],
|
level: [1, 2, 3, 4],
|
||||||
slugify: eleventyConfig.getFilter("slugify")
|
slugify: eleventyConfig.getFilter("slugify")
|
||||||
}).use(markdownItFootnote).use(mdfigcaption, figoptions);
|
})
|
||||||
|
.use(markdownItFootnote)
|
||||||
|
.use(mdfigcaption, figoptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eleventyConfig.setLibrary("md", mdLib);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
templateFormats: [
|
templateFormats: [
|
||||||
"md",
|
"md",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"name": "nathanupchurch.com",
|
"name": "nathanupchurch.com",
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"description": "The personal website and blog of Nathan Upchurch",
|
"description": "The personal website and blog of Nathan Upchurch",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx @11ty/eleventy",
|
"build": "npx @11ty/eleventy",
|
||||||
"build-ghpages": "npx @11ty/eleventy --pathprefix=/eleventy-base-blog/",
|
"build-ghpages": "npx @11ty/eleventy --pathprefix=/eleventy-base-blog/",
|
||||||
@ -28,8 +29,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://upchur.ch/gitea/n_u/nathanupchurch.com",
|
"homepage": "https://upchur.ch/gitea/n_u/nathanupchurch.com",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@11ty/eleventy": "^2.0.1",
|
"@11ty/eleventy": "3.0.0-alpha.13",
|
||||||
"@11ty/eleventy-img": "^3.1.0",
|
"@11ty/eleventy-img": "5.0.0-beta.10",
|
||||||
"@11ty/eleventy-navigation": "^0.3.5",
|
"@11ty/eleventy-navigation": "^0.3.5",
|
||||||
"@11ty/eleventy-plugin-bundle": "^1.0.4",
|
"@11ty/eleventy-plugin-bundle": "^1.0.4",
|
||||||
"@11ty/eleventy-plugin-rss": "^1.2.0",
|
"@11ty/eleventy-plugin-rss": "^1.2.0",
|
||||||
@ -41,6 +42,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"markdown-it-footnote": "^3.0.3",
|
"markdown-it-footnote": "^3.0.3",
|
||||||
"markdown-it-image-figures": "^2.1.1"
|
"markdown-it-image-figures": "^2.1.1",
|
||||||
|
"prettier": "^3.2.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
89
public/ai.txt
Normal file
89
public/ai.txt
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Spawning AI
|
||||||
|
# Prevent datasets from using the following file types
|
||||||
|
|
||||||
|
User-Agent: *
|
||||||
|
Disallow: *.txt
|
||||||
|
Disallow: *.pdf
|
||||||
|
Disallow: *.doc
|
||||||
|
Disallow: *.docx
|
||||||
|
Disallow: *.odt
|
||||||
|
Disallow: *.rtf
|
||||||
|
Disallow: *.tex
|
||||||
|
Disallow: *.wks
|
||||||
|
Disallow: *.wpd
|
||||||
|
Disallow: *.wps
|
||||||
|
Disallow: *.html
|
||||||
|
Disallow: *.bmp
|
||||||
|
Disallow: *.gif
|
||||||
|
Disallow: *.ico
|
||||||
|
Disallow: *.jpeg
|
||||||
|
Disallow: *.jpg
|
||||||
|
Disallow: *.png
|
||||||
|
Disallow: *.svg
|
||||||
|
Disallow: *.avif
|
||||||
|
Disallow: *.tif
|
||||||
|
Disallow: *.tiff
|
||||||
|
Disallow: *.webp
|
||||||
|
Disallow: *.aac
|
||||||
|
Disallow: *.aiff
|
||||||
|
Disallow: *.amr
|
||||||
|
Disallow: *.flac
|
||||||
|
Disallow: *.m4a
|
||||||
|
Disallow: *.mp3
|
||||||
|
Disallow: *.oga
|
||||||
|
Disallow: *.opus
|
||||||
|
Disallow: *.wav
|
||||||
|
Disallow: *.wma
|
||||||
|
Disallow: *.mp4
|
||||||
|
Disallow: *.webm
|
||||||
|
Disallow: *.ogg
|
||||||
|
Disallow: *.avi
|
||||||
|
Disallow: *.mov
|
||||||
|
Disallow: *.wmv
|
||||||
|
Disallow: *.flv
|
||||||
|
Disallow: *.mkv
|
||||||
|
Disallow: *.py
|
||||||
|
Disallow: *.js
|
||||||
|
Disallow: *.java
|
||||||
|
Disallow: *.c
|
||||||
|
Disallow: *.cpp
|
||||||
|
Disallow: *.cs
|
||||||
|
Disallow: *.h
|
||||||
|
Disallow: *.css
|
||||||
|
Disallow: *.php
|
||||||
|
Disallow: *.swift
|
||||||
|
Disallow: *.go
|
||||||
|
Disallow: *.rb
|
||||||
|
Disallow: *.pl
|
||||||
|
Disallow: *.sh
|
||||||
|
Disallow: *.sql
|
||||||
|
Disallow: /
|
||||||
|
Disallow: *
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@( @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@% @@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@( @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@ %@@@@@/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@ %@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@, @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@( %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
|
||||||
|
# @@@@@@@@@@@/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@, @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@ %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@ %@@@@& @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@ .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ spawning.ai
|
@ -1,10 +1,9 @@
|
|||||||
code[class*="language-"],
|
code[class*="language-"], pre[class*="language-"] {
|
||||||
pre[class*="language-"] {
|
|
||||||
color: #f8f8f2;
|
color: #f8f8f2;
|
||||||
background: none;
|
background: none;
|
||||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||||
font-family: var(--font-family-monospace);
|
font-family: var(--font-family-monospace);
|
||||||
font-size: var(--font-n);
|
font-size: var(--step--1);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
word-spacing: normal;
|
word-spacing: normal;
|
||||||
@ -12,9 +11,9 @@ pre[class*="language-"] {
|
|||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
||||||
-moz-tab-size: 4;
|
-moz-tab-size: 2;
|
||||||
-o-tab-size: 4;
|
-o-tab-size: 2;
|
||||||
tab-size: 4;
|
tab-size: 2;
|
||||||
|
|
||||||
-webkit-hyphens: none;
|
-webkit-hyphens: none;
|
||||||
-moz-hyphens: none;
|
-moz-hyphens: none;
|
||||||
@ -24,7 +23,7 @@ pre[class*="language-"] {
|
|||||||
|
|
||||||
/* Code blocks */
|
/* Code blocks */
|
||||||
pre[class*="language-"] {
|
pre[class*="language-"] {
|
||||||
padding: var(--single-gap);
|
padding: var(--space-2xs);
|
||||||
margin: .5em 0;
|
margin: .5em 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-radius: 0.3em;
|
border-radius: 0.3em;
|
||||||
@ -121,7 +120,7 @@ pre[class*="language-diff-"] {
|
|||||||
padding-left: var(--eleventy-code-padding);
|
padding-left: var(--eleventy-code-padding);
|
||||||
padding-right: var(--eleventy-code-padding);
|
padding-right: var(--eleventy-code-padding);
|
||||||
font-family: var(--font-family-monospace);
|
font-family: var(--font-family-monospace);
|
||||||
font-size: 1em;
|
font-size: var(--step--1);
|
||||||
}
|
}
|
||||||
.token.deleted {
|
.token.deleted {
|
||||||
background-color: hsl(0, 51%, 37%);
|
background-color: hsl(0, 51%, 37%);
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
main > p:not(.nodropcap):first-of-type:first-letter {
|
|
||||||
float: left;
|
|
||||||
font-size: 4rem;
|
|
||||||
font-weight: 300;
|
|
||||||
line-height: 60px;
|
|
||||||
padding: .5rem .5rem .5rem .5rem;
|
|
||||||
color: var(--contrast-color);
|
|
||||||
border: solid 2px var(--contrast-color);
|
|
||||||
font-family: var(--font-family-headline);
|
|
||||||
font-weight: 700;
|
|
||||||
margin-right: .25em;
|
|
||||||
border-radius: .2em;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
margin-bottom: -.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(max-width: 556px) {
|
|
||||||
main > p:not(.nodropcap):first-of-type:first-letter {
|
|
||||||
font-size: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,68 +0,0 @@
|
|||||||
html.barebones, body.barebones {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1.socialTitle {
|
|
||||||
padding-top: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.profilePic {
|
|
||||||
max-width: 10em;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: solid 5px var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.page-block.nodropcap {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.socialLinks {
|
|
||||||
padding-left: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socialLinks li {
|
|
||||||
background-color: var(--nav-pill-background-color-inactive);
|
|
||||||
color: var(--nav-pill-text-color);
|
|
||||||
border-radius: var(--pill-radius);
|
|
||||||
padding: .5em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
list-style-type: none;
|
|
||||||
transition: all var(--transition-time);
|
|
||||||
width: 20em;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socialLinks li:hover {
|
|
||||||
background-color: var(--contrast-color);
|
|
||||||
transform: scale(1.05, 1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.socialLinks a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socialLinks a img {
|
|
||||||
max-width: 1em;
|
|
||||||
margin-right: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(max-width: 556px) {
|
|
||||||
.socialLinks li {
|
|
||||||
width: 90vw;
|
|
||||||
height: 3rem;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
30
public/css/me.css
Normal file
30
public/css/me.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* /Me */
|
||||||
|
|
||||||
|
.links-container {
|
||||||
|
grid-column: 1 / span 12;
|
||||||
|
padding: 0 var(--space-3xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.socialLinks a button {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 var(--space-s) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.socialTitle {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--step-4);
|
||||||
|
line-height: calc(var(--step-4) * 0.5 + var(--step-4));
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-container p {
|
||||||
|
font-size: var(--step-0);
|
||||||
|
line-height: calc(var(--step-0) * 0.5 + var(--step-0));
|
||||||
|
padding-top: var(--space-3xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
img.profilePic {
|
||||||
|
max-width: var(--space-3xl);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: solid 2px var(--text-color);
|
||||||
|
}
|
BIN
public/css/webfonts/Fraunces-Italic[SOFT,WONK,opsz,wght].woff2
Normal file
BIN
public/css/webfonts/Fraunces-Italic[SOFT,WONK,opsz,wght].woff2
Normal file
Binary file not shown.
BIN
public/css/webfonts/Fraunces[SOFT,WONK,opsz,wght].woff2
Normal file
BIN
public/css/webfonts/Fraunces[SOFT,WONK,opsz,wght].woff2
Normal file
Binary file not shown.
BIN
public/css/webfonts/Manrope[wght].woff2
Normal file
BIN
public/css/webfonts/Manrope[wght].woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user