<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>auxdev.net</title>
    <subtitle>Dev notes by Tim. Backend, devsecops, homelab, and whatever else fell off the shelf.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://auxdev.net/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://auxdev.net"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-22T00:00:00+00:00</updated>
    <id>https://auxdev.net/atom.xml</id>
    <entry xml:lang="en">
        <title>I made a Zola theme and now you have to deal with it</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://auxdev.net/blog/announcing-basalt/"/>
        <id>https://auxdev.net/blog/announcing-basalt/</id>
        
        <content type="html" xml:base="https://auxdev.net/blog/announcing-basalt/">&lt;p&gt;I spent too many weekends on a Zola theme, and here we are.&lt;&#x2F;p&gt;
&lt;p&gt;The result is &lt;a href=&quot;&#x2F;projects&#x2F;basalt&#x2F;&quot;&gt;basalt&lt;&#x2F;a&gt;, a Zola theme with small page sizes, readable type, and defaults that don&#x27;t make me argue with the config file. Two sites run on it: this one (terminal-themed, vaguely angry) and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;timsiggins.com&quot;&gt;timsiggins.com&lt;&#x2F;a&gt; (editorial, vaguely respectable). Same theme, two completely different identities, one shared &lt;code&gt;[extra.persona.*]&lt;&#x2F;code&gt; token table.&lt;&#x2F;p&gt;
&lt;p&gt;Want to poke at it? Source is at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt&quot;&gt;codeberg.org&#x2F;ttsigg&#x2F;basalt&lt;&#x2F;a&gt;, the project page lives at &lt;a href=&quot;&#x2F;projects&#x2F;basalt&#x2F;&quot;&gt;&#x2F;projects&#x2F;basalt&#x2F;&lt;&#x2F;a&gt;, and the README covers install.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-ships-in-v1&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-ships-in-v1&quot; aria-label=&quot;Permalink to what-ships-in-v1&quot;&gt;&lt;&#x2F;a&gt;
What ships in v1&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Small pages.&lt;&#x2F;strong&gt; Under 40kb per page on almost every route, fonts excluded. The footer prints the actual number on every load. No tracking pixels, no analytics blob, no cookie banner. Body copy is variable Inter (or Public Sans, on ts.com), headings are Space Grotesk (or Newsreader, ts.com), code is JetBrains Mono. All variable WOFF2, Latin subset, cached for a year.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;No build step in the consumer repo.&lt;&#x2F;strong&gt; The JS is a Vite bundle that ships from the theme&#x27;s &lt;code&gt;static&#x2F;dist&#x2F;&lt;&#x2F;code&gt; as a committed artifact. Consumer sites don&#x27;t run &lt;code&gt;pnpm install&lt;&#x2F;code&gt;. If you want to recompile, the theme repo has a justfile.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Light and dark, no flash.&lt;&#x2F;strong&gt; Theme preference lives in &lt;code&gt;localStorage&lt;&#x2F;code&gt;. Boot script is inlined in &lt;code&gt;&amp;lt;head&amp;gt;&lt;&#x2F;code&gt; and runs before paint, so there&#x27;s no wrong-theme-flash even on a slow connection. Press &lt;kbd&gt;t&lt;&#x2F;kbd&gt; to toggle.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Command palette on &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; Searches post titles and bodies via Zola&#x27;s built-in index. Lazy-loads on first keypress, about 3kb on the wire. &lt;kbd&gt;Ctrl+K&lt;&#x2F;kbd&gt; works too if your muscle memory disagrees with the vim default.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Asciinema and KaTeX, pay-per-use.&lt;&#x2F;strong&gt; Both only load on pages that use them. A post with no math pays nothing for KaTeX. A post with no terminal recording pays nothing for asciinema.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Mastodon and Bluesky comments.&lt;&#x2F;strong&gt; Opt-in per post, lazy-loaded on scroll-into-view. No third-party script until the reader gets there. No cookies. No consent banner. Live demos: &lt;a href=&quot;&#x2F;blog&#x2F;comments-on-mastodon&#x2F;&quot;&gt;Mastodon&lt;&#x2F;a&gt;, &lt;a href=&quot;&#x2F;blog&#x2F;comments-on-bluesky&#x2F;&quot;&gt;Bluesky&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Responsive images out of the box.&lt;&#x2F;strong&gt; The &lt;code&gt;image&lt;&#x2F;code&gt; shortcode emits a &lt;code&gt;srcset&lt;&#x2F;code&gt; with WebP variants via Zola&#x27;s &lt;code&gt;resize_image()&lt;&#x2F;code&gt; at build time. Optional &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;&#x2F;code&gt; lightbox, optional left&#x2F;right float, all driven by one shortcode.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Eleven shortcodes, all documented.&lt;&#x2F;strong&gt; alert, collapsible, em, toc, image, carousel, asciinema, katex, mermaid, project, plus pullquote and epigraph for editorial breaks. Every one has a no-JS fallback. Full demo lives in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt&#x2F;src&#x2F;branch&#x2F;main&#x2F;content&#x2F;options&#x2F;&quot;&gt;basalt content&#x2F;options&#x2F;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;personas&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#personas&quot; aria-label=&quot;Permalink to personas&quot;&gt;&lt;&#x2F;a&gt;
Personas&lt;&#x2F;h2&gt;
&lt;p&gt;The thing I rewrote six times is the persona system. Both consumer sites speak the same theme but render hella differently:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Token&lt;&#x2F;th&gt;&lt;th&gt;auxdev (this site)&lt;&#x2F;th&gt;&lt;th&gt;timsiggins.com&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--font-text&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Inter&lt;&#x2F;td&gt;&lt;td&gt;Public Sans&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--font-display&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Space Grotesk&lt;&#x2F;td&gt;&lt;td&gt;Newsreader&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--ornament&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;▍   ▍   ▍&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;⁂&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--anchor-glyph&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;❯&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;§&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Accent&lt;&#x2F;td&gt;&lt;td&gt;terminal green&lt;&#x2F;td&gt;&lt;td&gt;editorial indigo&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pullquote font&lt;&#x2F;td&gt;&lt;td&gt;mono italic&lt;&#x2F;td&gt;&lt;td&gt;serif italic&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Both sites import the theme once via SCSS aggregator, then layer their own &lt;code&gt;[extra.persona.*]&lt;&#x2F;code&gt; token table for colours and fonts, plus a &lt;code&gt;templates&#x2F;partials&#x2F;site-head.html&lt;&#x2F;code&gt; for any inline head injection. That&#x27;s the whole customisation surface.&lt;&#x2F;p&gt;

&lt;aside class=&quot;pullquote&quot;&gt;
  &lt;blockquote class=&quot;pullquote__body&quot;&gt;Two visual identities, one engine. The interesting work is what stays the same.&lt;&#x2F;blockquote&gt;&lt;&#x2F;aside&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Permalink to what-it-costs&quot;&gt;&lt;&#x2F;a&gt;
What it costs&lt;&#x2F;h2&gt;
&lt;p&gt;Rounded, as served, gzipped:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Home page HTML: about 8kb&lt;&#x2F;li&gt;
&lt;li&gt;Blog post HTML: about 9kb, plus the post body&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;main.css&lt;&#x2F;code&gt;: about 16kb&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;theme.js&lt;&#x2F;code&gt; (inlined, every page): about 1kb&lt;&#x2F;li&gt;
&lt;li&gt;Vite main bundle (deferred, every page): about 4kb&lt;&#x2F;li&gt;
&lt;li&gt;Lazy chunks (palette, comments, katex, asciinema): 1-3kb each, only on demand&lt;&#x2F;li&gt;
&lt;li&gt;Fonts, first visit: about 110kb across three WOFF2 files, then cached for a year&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A cold first visit is comfortably under 200kb. A repeat visit, once fonts are cached, is about 25kb. The site stays in the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;512kb.club&quot;&gt;512kb.club&lt;&#x2F;a&gt; green tier on every route. If you find a route that doesn&#x27;t, file an issue.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-on-the-roadmap&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-s-on-the-roadmap&quot; aria-label=&quot;Permalink to what-s-on-the-roadmap&quot;&gt;&lt;&#x2F;a&gt;
What&#x27;s on the roadmap&lt;&#x2F;h2&gt;
&lt;p&gt;I ship theme updates when I remember to, not on a cadence. Loosely ordered by how much I want each one:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A &quot;year in review&quot; archive page.&lt;&#x2F;strong&gt; Group posts by year, sparkline of post frequency, link out. One Tera template and a small SCSS file.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tag-prefix search.&lt;&#x2F;strong&gt; The palette searches title and body; I want a &lt;code&gt;#rust&lt;&#x2F;code&gt;-style prefix that filters on tags too.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A &quot;printable&quot; view.&lt;&#x2F;strong&gt; Strip the sidebar, flatten the colours, reflow to a paper width. Mostly for when someone sends me a post and I want it on my Kindle.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Webmention sender + receiver.&lt;&#x2F;strong&gt; Mastodon and Bluesky comments are neat; a webmention or two would be neater.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Anything else is a maybe. Repo issues are open.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;using-it&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#using-it&quot; aria-label=&quot;Permalink to using-it&quot;&gt;&lt;&#x2F;a&gt;
Using it&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;git&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; clone&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; my-blog-theme&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;git&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; clone&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;auxdev.net&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; my-blog&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; my-blog&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;ln&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ..&#x2F;my-blog-theme&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; themes&#x2F;basalt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;   #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; symlink for now; submodule wiring on the way&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;just&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; setup&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;just&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; serve&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Point &lt;code&gt;config.toml&lt;&#x2F;code&gt; at yourself, drop your content into &lt;code&gt;content&#x2F;&lt;&#x2F;code&gt;, and &lt;code&gt;just build&lt;&#x2F;code&gt;. The DEPLOY notes for Codeberg + Cloudflare Workers live in the auxdev repo.&lt;&#x2F;p&gt;
&lt;p&gt;If you do something neat with it, let me know.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>basalt</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://auxdev.net/projects/basalt/"/>
        <id>https://auxdev.net/projects/basalt/</id>
        
        <content type="html" xml:base="https://auxdev.net/projects/basalt/">&lt;p&gt;The theme behind this site and behind &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;timsiggins.com&quot;&gt;timsiggins.com&lt;&#x2F;a&gt;. Monospace-accented or editorial depending on the persona, keyboard-friendly, light and dark, small enough to serve from a potato. No framework in the consumer repo. JavaScript off? Everything still works except the command palette and the comment embeds.&lt;&#x2F;p&gt;
&lt;p&gt;Source lives at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt&quot;&gt;codeberg.org&#x2F;ttsigg&#x2F;basalt&lt;&#x2F;a&gt;. The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt#readme&quot;&gt;README&lt;&#x2F;a&gt; has install notes; the &lt;a href=&quot;&#x2F;blog&#x2F;announcing-basalt&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; covers what shipped in v1.&lt;&#x2F;p&gt;

&lt;div class=&quot;card&quot;&gt;
  &lt;h3&gt;&lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;basalt&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
  &lt;div class=&quot;mono card-meta&quot;&gt;
    
    
  &lt;&#x2F;div&gt;
  
  
&lt;&#x2F;div&gt;

&lt;h2 id=&quot;what-you-get&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-you-get&quot; aria-label=&quot;Permalink to what-you-get&quot;&gt;&lt;&#x2F;a&gt;
What you get&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;btop-inspired panes&lt;&#x2F;strong&gt; on the homepage and &lt;code&gt;&#x2F;now&lt;&#x2F;code&gt;, with a live uptime clock, a reading list, and a tinkering-with card driven by a single &lt;code&gt;data&#x2F;now.json&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Command palette&lt;&#x2F;strong&gt; on &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;. Searches titles and body text via Zola&#x27;s built-in index, ships at about 3kb, lazy-loads on first keypress. &lt;kbd&gt;Ctrl+K&lt;&#x2F;kbd&gt; works too.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Light and dark themes&lt;&#x2F;strong&gt; with no flash of wrong theme, ever. Press &lt;kbd&gt;t&lt;&#x2F;kbd&gt; to toggle.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Eleven shortcodes&lt;&#x2F;strong&gt; with full no-JS fallbacks: alert, collapsible, em, toc, image (with float + lightbox), carousel, asciinema, katex, mermaid (markdown fence), project, pullquote, epigraph.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Asciinema embeds&lt;&#x2F;strong&gt; with a titlebar that matches the site. Player loads only on pages that use the shortcode.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;KaTeX&lt;&#x2F;strong&gt; for math. Loaded only on pages that contain math.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Mastodon and Bluesky comments&lt;&#x2F;strong&gt;, opt-in per post, lazy-loaded on scroll-into-view.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Responsive images&lt;&#x2F;strong&gt; via Zola&#x27;s &lt;code&gt;resize_image()&lt;&#x2F;code&gt; with multi-width WebP &lt;code&gt;srcset&lt;&#x2F;code&gt; out of the box.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MPA View Transitions&lt;&#x2F;strong&gt; for smooth navigation in browsers that support them, gated on &lt;code&gt;prefers-reduced-motion&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Persona token system.&lt;&#x2F;strong&gt; Two sites running on this theme look completely different — they swap fonts, colours, ornaments, and editorial chrome via a &lt;code&gt;[extra.persona.*]&lt;&#x2F;code&gt; table.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Under 40kb per page&lt;&#x2F;strong&gt; on almost every route, fonts included.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;figure class=&quot;image-shortcode &quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; href=&quot;&#x2F;screenshots&#x2F;home-desktop-light.png&quot; data-lightbox=&quot;&#x2F;screenshots&#x2F;home-desktop-light.png&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-desktop-light.871cb4828d974990.png&quot; srcset=&quot;https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;home-desktop-light.d25e3781a4ab88a9.png 400w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;home-desktop-light.871cb4828d974990.png 800w&quot; sizes=&quot;(max-width: 800px) 100vw, 800px&quot; width=&quot;800&quot; height=&quot;600&quot; alt=&quot;basalt homepage on auxdev.net, desktop, light theme&quot;
    loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;&#x2F;a&gt;&lt;figcaption&gt;auxdev&#x27;s persona — terminal green, mono accents.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;image-shortcode &quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; href=&quot;&#x2F;screenshots&#x2F;home-desktop-dark.png&quot; data-lightbox=&quot;&#x2F;screenshots&#x2F;home-desktop-dark.png&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-desktop-dark.4cae7dfde7d3be55.png&quot; srcset=&quot;https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;home-desktop-dark.27a95a5510ba25d6.png 400w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;home-desktop-dark.4cae7dfde7d3be55.png 800w&quot; sizes=&quot;(max-width: 800px) 100vw, 800px&quot; width=&quot;800&quot; height=&quot;600&quot; alt=&quot;basalt homepage on auxdev.net, desktop, dark theme&quot;
    loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;&#x2F;a&gt;&lt;figcaption&gt;The same homepage in dark mode.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;two-personas-one-engine&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#two-personas-one-engine&quot; aria-label=&quot;Permalink to two-personas-one-engine&quot;&gt;&lt;&#x2F;a&gt;
Two personas, one engine&lt;&#x2F;h2&gt;
&lt;p&gt;The part that took six rewrites is the persona system. Both consumer sites import basalt&#x27;s SCSS aggregator once, then layer their own token table:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Token&lt;&#x2F;th&gt;&lt;th&gt;auxdev (this site)&lt;&#x2F;th&gt;&lt;th&gt;timsiggins.com&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--font-text&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Inter&lt;&#x2F;td&gt;&lt;td&gt;Public Sans&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--font-display&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Space Grotesk&lt;&#x2F;td&gt;&lt;td&gt;Newsreader&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--ornament&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;▍   ▍   ▍&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;⁂&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--anchor-glyph&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;❯&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;§&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Accent&lt;&#x2F;td&gt;&lt;td&gt;terminal green&lt;&#x2F;td&gt;&lt;td&gt;editorial indigo&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pullquote chrome&lt;&#x2F;td&gt;&lt;td&gt;mono italic, centered&lt;&#x2F;td&gt;&lt;td&gt;serif italic, large&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Same Tera templates, same Vite bundle, two wildly different identities. Adding a third consumer site is a &lt;code&gt;config.toml&lt;&#x2F;code&gt; away.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-blog-post-light-and-dark&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#a-blog-post-light-and-dark&quot; aria-label=&quot;Permalink to a-blog-post-light-and-dark&quot;&gt;&lt;&#x2F;a&gt;
A blog post, light and dark&lt;&#x2F;h2&gt;
&lt;figure class=&quot;image-shortcode &quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; href=&quot;&#x2F;screenshots&#x2F;post-desktop-light.png&quot; data-lightbox=&quot;&#x2F;screenshots&#x2F;post-desktop-light.png&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;post-desktop-light.16dd3a60e8d7bd96.png&quot; srcset=&quot;https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;post-desktop-light.793cf71ee1cfdf47.png 400w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;post-desktop-light.16dd3a60e8d7bd96.png 800w&quot; sizes=&quot;(max-width: 800px) 100vw, 800px&quot; width=&quot;800&quot; height=&quot;600&quot; alt=&quot;basalt blog post, desktop, light theme&quot;
    loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;&#x2F;a&gt;&lt;figcaption&gt;Blog post, desktop, light theme.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;figure class=&quot;image-shortcode &quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; href=&quot;&#x2F;screenshots&#x2F;post-code-dark.png&quot; data-lightbox=&quot;&#x2F;screenshots&#x2F;post-code-dark.png&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;post-code-dark.92e21d386e5bccfa.png&quot; srcset=&quot;https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;post-code-dark.fe4743836f2782f3.png 400w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;post-code-dark.92e21d386e5bccfa.png 800w&quot; sizes=&quot;(max-width: 800px) 100vw, 800px&quot; width=&quot;800&quot; height=&quot;600&quot; alt=&quot;basalt code block rendering, dark theme&quot;
    loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;&#x2F;a&gt;&lt;figcaption&gt;Syntax highlighting via giallo, class-based, paired light and dark themes.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;mobile&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#mobile&quot; aria-label=&quot;Permalink to mobile&quot;&gt;&lt;&#x2F;a&gt;
Mobile&lt;&#x2F;h2&gt;
&lt;p&gt;Same content, narrower viewport. Sidebar collapses to a top bar; panes stack; the command palette still works on phones with a hardware keyboard.&lt;&#x2F;p&gt;
&lt;figure class=&quot;carousel-wrap&quot;&gt;
  &lt;div class=&quot;carousel&quot;
       data-carousel
       role=&quot;region&quot;
       aria-roledescription=&quot;carousel&quot;
       aria-label=&quot;Mobile homepage, both themes.&quot;
       tabindex=&quot;0&quot;&gt;

    &lt;ul class=&quot;carousel-track&quot; role=&quot;list&quot;&gt;&lt;li class=&quot;carousel-slide&quot; role=&quot;listitem&quot; aria-label=&quot;Slide 1 of 2&quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; data-lightbox=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-mobile-light.10447499fbae64bf.webp&quot; href=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-mobile-light.10447499fbae64bf.webp&quot; aria-label=&quot;basalt mobile — open larger view&quot;&gt;
            &lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-mobile-light.b235105506f6b1fd.webp&quot; width=&quot;800&quot; height=&quot;1732&quot; alt=&quot;basalt mobile&quot;
              loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
          &lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li class=&quot;carousel-slide&quot; role=&quot;listitem&quot; aria-label=&quot;Slide 2 of 2&quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; data-lightbox=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-mobile-dark.b72a2ee8e88a2175.webp&quot; href=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-mobile-dark.b72a2ee8e88a2175.webp&quot; aria-label=&quot;light theme — open larger view&quot;&gt;
            &lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;home-mobile-dark.9efb9e78632cad3a.webp&quot; width=&quot;800&quot; height=&quot;1732&quot; alt=&quot;light theme&quot;
              loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
          &lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ul&gt;

    &lt;button class=&quot;carousel-nav carousel-prev&quot; type=&quot;button&quot; aria-label=&quot;Previous slide&quot; hidden&gt;
      &lt;svg class=&quot;icon&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot;&gt;&lt;use href=&quot;#icon-chevron&quot;&#x2F;&gt;&lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
    &lt;button class=&quot;carousel-nav carousel-next&quot; type=&quot;button&quot; aria-label=&quot;Next slide&quot; hidden&gt;
      &lt;svg class=&quot;icon&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot;&gt;&lt;use href=&quot;#icon-chevron&quot;&#x2F;&gt;&lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;

    &lt;ol class=&quot;carousel-dots&quot; role=&quot;list&quot; hidden&gt;&lt;li&gt;&lt;button class=&quot;carousel-dot&quot; type=&quot;button&quot; data-slide=&quot;0&quot; aria-label=&quot;Go to slide 1&quot;&gt;&lt;&#x2F;button&gt;&lt;&#x2F;li&gt;&lt;li&gt;&lt;button class=&quot;carousel-dot&quot; type=&quot;button&quot; data-slide=&quot;1&quot; aria-label=&quot;Go to slide 2&quot;&gt;&lt;&#x2F;button&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;
  &lt;&#x2F;div&gt;&lt;figcaption class=&quot;carousel-caption&quot;&gt;Mobile homepage, both themes.&lt;&#x2F;figcaption&gt;&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;a-terminal-recording-because-why-not&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#a-terminal-recording-because-why-not&quot; aria-label=&quot;Permalink to a-terminal-recording-because-why-not&quot;&gt;&lt;&#x2F;a&gt;
A terminal recording, because why not&lt;&#x2F;h2&gt;
&lt;p&gt;The asciinema shortcode renders a scaled player with a themed titlebar. The player and runtime are lazy-loaded only on pages that have a cast.&lt;&#x2F;p&gt;
&lt;figure class=&quot;asciinema-wrap&quot;&gt;
  &lt;figcaption class=&quot;asciinema-titlebar mono&quot;&gt;
    &lt;span class=&quot;asciinema-dot dot-dim&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;
    &lt;span class=&quot;asciinema-title-text&quot;&gt;asciinema &amp;#x2F;&amp;#x2F; demo session&lt;&#x2F;span&gt;
    &lt;span class=&quot;asciinema-spacer&quot;&gt;&lt;&#x2F;span&gt;
    
  &lt;&#x2F;figcaption&gt;

  &lt;div class=&quot;asciinema-player&quot;
       id=&quot;player--casts-658148-cast&quot;
       data-asciinema-cast=&quot;&#x2F;casts&#x2F;658148.cast&quot;
       data-asciinema-theme=&quot;basalt&quot;
       data-asciinema-fit=&quot;width&quot;&gt;
    
    
    &lt;noscript&gt;
      &lt;div class=&quot;asciinema-fallback asciinema-fallback-empty&quot;&gt;
        &lt;a class=&quot;link&quot; href=&quot;&amp;#x2F;casts&amp;#x2F;658148.cast&quot; download&gt;download cast file &amp;#8599;&lt;&#x2F;a&gt;
      &lt;&#x2F;div&gt;
    &lt;&#x2F;noscript&gt;
    
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;math-lazy-loaded&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#math-lazy-loaded&quot; aria-label=&quot;Permalink to math-lazy-loaded&quot;&gt;&lt;&#x2F;a&gt;
Math, lazy-loaded&lt;&#x2F;h2&gt;
&lt;p&gt;Inline KaTeX: &lt;script type=&quot;math&#x2F;tex&quot;&gt;a^2 + b^2 = c^2&lt;&#x2F;script&gt;
. Display mode:&lt;&#x2F;p&gt;
&lt;script type=&quot;math&#x2F;tex;mode=display&quot;&gt;\rho \left( \frac{\partial \mathbf{u}}{\partial t} + (\mathbf{u} \cdot \nabla)\mathbf{u} \right) = -\nabla p + \mu \nabla^2 \mathbf{u} + \rho \mathbf{g}&lt;&#x2F;script&gt;
&lt;p&gt;KaTeX is loaded only on pages whose content contains a math element. Pages without math never pay the cost.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comments-two-flavours&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#comments-two-flavours&quot; aria-label=&quot;Permalink to comments-two-flavours&quot;&gt;&lt;&#x2F;a&gt;
Comments, two flavours&lt;&#x2F;h2&gt;
&lt;p&gt;Opt-in per post via front-matter:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;+++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;comments&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-string z-quoted&quot;&gt;bluesky&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;bluesky&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-string z-quoted&quot;&gt;https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;handle&#x2F;post&#x2F;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;+++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Both renderers lazy-load the platform&#x27;s public API when the reader scrolls to the bottom. No cookies, no third-party script until then, no consent banner. Live demos: &lt;a href=&quot;&#x2F;blog&#x2F;comments-on-mastodon&#x2F;&quot;&gt;Mastodon&lt;&#x2F;a&gt;, &lt;a href=&quot;&#x2F;blog&#x2F;comments-on-bluesky&#x2F;&quot;&gt;Bluesky&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tech&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#tech&quot; aria-label=&quot;Permalink to tech&quot;&gt;&lt;&#x2F;a&gt;
Tech&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zola 0.22.1&lt;&#x2F;strong&gt; — static generator and built-in Sass.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tera&lt;&#x2F;strong&gt; — templates, because Zola ships Tera.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Sass&lt;&#x2F;strong&gt; with CSS custom properties for theming.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Vite + Rollup&lt;&#x2F;strong&gt; — JS bundle. Output ships as a committed artifact in the theme repo, so consumer sites don&#x27;t need npm.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Playwright&lt;&#x2F;strong&gt; — cross-site visual-consistency suite.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;using-it&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#using-it&quot; aria-label=&quot;Permalink to using-it&quot;&gt;&lt;&#x2F;a&gt;
Using it&lt;&#x2F;h2&gt;
&lt;p&gt;Clone the repo, drop your content into &lt;code&gt;content&#x2F;&lt;&#x2F;code&gt;, point &lt;code&gt;config.toml&lt;&#x2F;code&gt; at yourself, run &lt;code&gt;just setup&lt;&#x2F;code&gt; once to fetch Zola, then &lt;code&gt;just serve&lt;&#x2F;code&gt;. Production builds go to &lt;code&gt;public&#x2F;&lt;&#x2F;code&gt; via &lt;code&gt;just build&lt;&#x2F;code&gt;. The auxdev repo&#x27;s DEPLOY notes walk through Cloudflare Workers from scratch.&lt;&#x2F;p&gt;
&lt;p&gt;If you hit something busted, file a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;ttsigg&#x2F;basalt&#x2F;issues&quot;&gt;Codeberg issue&lt;&#x2F;a&gt;. I read them.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Comments, loaded from Mastodon</title>
        <published>2026-04-20T00:00:00+00:00</published>
        <updated>2026-04-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://auxdev.net/blog/comments-on-mastodon/"/>
        <id>https://auxdev.net/blog/comments-on-mastodon/</id>
        
        <content type="html" xml:base="https://auxdev.net/blog/comments-on-mastodon/">&lt;p&gt;Live demo of the Mastodon comment renderer that ships with &lt;a href=&quot;&#x2F;projects&#x2F;basalt&#x2F;&quot;&gt;basalt&lt;&#x2F;a&gt;. Scroll to the bottom and the thread populates from the instance&#x27;s public API. If you turn JavaScript off, nothing loads but the link still works.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-the-renderer-behaves&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#how-the-renderer-behaves&quot; aria-label=&quot;Permalink to how-the-renderer-behaves&quot;&gt;&lt;&#x2F;a&gt;
How the renderer behaves&lt;&#x2F;h2&gt;
&lt;p&gt;Opt-in per post via front matter:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;+++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;comments&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-string z-quoted&quot;&gt;mastodon&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;mastodon&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-string z-quoted&quot;&gt;https:&#x2F;&#x2F;your.server&#x2F;@handle&#x2F;123456789&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;+++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The template at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ttsigg&#x2F;auxdev.net&#x2F;blob&#x2F;main&#x2F;templates&#x2F;partials&#x2F;comments.html&quot;&gt;templates&#x2F;partials&#x2F;comments.html&lt;&#x2F;a&gt; checks &lt;code&gt;page.extra.comments&lt;&#x2F;code&gt;, emits an empty &lt;code&gt;&amp;lt;section id=&quot;comments&quot;&amp;gt;&lt;&#x2F;code&gt; with a &lt;code&gt;data-url&lt;&#x2F;code&gt; attribute, then stops. No API call fires during page render.&lt;&#x2F;p&gt;
&lt;p&gt;The small &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ttsigg&#x2F;auxdev.net&#x2F;blob&#x2F;main&#x2F;static&#x2F;js&#x2F;comments-loader.js&quot;&gt;comments-loader.js&lt;&#x2F;a&gt; sets up an &lt;code&gt;IntersectionObserver&lt;&#x2F;code&gt; on &lt;code&gt;#comments&lt;&#x2F;code&gt;. When the section scrolls into view, it dynamically imports the platform-specific renderer: either &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ttsigg&#x2F;auxdev.net&#x2F;blob&#x2F;main&#x2F;static&#x2F;js&#x2F;comments-mastodon.js&quot;&gt;comments-mastodon.js&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ttsigg&#x2F;auxdev.net&#x2F;blob&#x2F;main&#x2F;static&#x2F;js&#x2F;comments-bluesky.js&quot;&gt;comments-bluesky.js&lt;&#x2F;a&gt;. Nothing else runs until the reader gets there.&lt;&#x2F;p&gt;
&lt;p&gt;The Mastodon renderer hits &lt;code&gt;GET &#x2F;api&#x2F;v1&#x2F;statuses&#x2F;:id&#x2F;context&lt;&#x2F;code&gt;, reads the &lt;code&gt;descendants&lt;&#x2F;code&gt; array, and renders a nested tree in the same mono type as the rest of the site. HTML content is sanitized with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cure53&#x2F;DOMPurify&quot;&gt;DOMPurify&lt;&#x2F;a&gt;, inlined on first use and cached afterwards.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-you-pay-for&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-you-pay-for&quot; aria-label=&quot;Permalink to what-you-pay-for&quot;&gt;&lt;&#x2F;a&gt;
What you pay for&lt;&#x2F;h2&gt;
&lt;p&gt;Nothing, until you scroll. On pages without the &lt;code&gt;comments = &quot;mastodon&quot;&lt;&#x2F;code&gt; front-matter, none of the comment scripts ship. On pages that do, the loader is about 300 bytes and the renderer is about 4kb, plus DOMPurify at about 22kb on first use. DOMPurify is the biggest single cost; I&#x27;m open to suggestions for a smaller sanitiser.&lt;&#x2F;p&gt;
&lt;p&gt;No tracking pixels, no cookies, no third-party iframes, no consent banner. The embedded thread is a plain DOM tree drawn from a public API response.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;alternative-flavours&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#alternative-flavours&quot; aria-label=&quot;Permalink to alternative-flavours&quot;&gt;&lt;&#x2F;a&gt;
Alternative flavours&lt;&#x2F;h2&gt;
&lt;p&gt;Prefer Bluesky? There&#x27;s a &lt;a href=&quot;&#x2F;blog&#x2F;comments-on-bluesky&#x2F;&quot;&gt;sister demo post&lt;&#x2F;a&gt; with the same flow against &lt;code&gt;app.bsky.feed.getPostThread&lt;&#x2F;code&gt;. The loader picks the renderer by the &lt;code&gt;data-platform&lt;&#x2F;code&gt; attribute on &lt;code&gt;#comments&lt;&#x2F;code&gt;, so swapping platforms is a one-line front-matter change.&lt;&#x2F;p&gt;
&lt;p&gt;Scroll down. The fediverse should do its thing in a moment.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Comments, loaded from Bluesky</title>
        <published>2026-04-19T00:00:00+00:00</published>
        <updated>2026-04-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://auxdev.net/blog/comments-on-bluesky/"/>
        <id>https://auxdev.net/blog/comments-on-bluesky/</id>
        
        <content type="html" xml:base="https://auxdev.net/blog/comments-on-bluesky/">&lt;p&gt;This is a live demo of the Bluesky comment renderer that ships with &lt;a href=&quot;&#x2F;projects&#x2F;basalt&#x2F;&quot;&gt;basalt&lt;&#x2F;a&gt;. Scroll to the bottom and the thread populates from the AT Protocol&#x27;s public appview. If you turn JavaScript off, the link at the top of the comment section still points at the canonical post.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#how-it-works&quot; aria-label=&quot;Permalink to how-it-works&quot;&gt;&lt;&#x2F;a&gt;
How it works&lt;&#x2F;h2&gt;
&lt;p&gt;Opt-in per post via front matter:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;+++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;comments&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-string z-quoted&quot;&gt;bluesky&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;bluesky&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-string z-quoted&quot;&gt;https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;handle&#x2F;post&#x2F;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;+++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At build time the template writes an empty &lt;code&gt;&amp;lt;section id=&quot;comments&quot;&amp;gt;&lt;&#x2F;code&gt; with a &lt;code&gt;data-platform=&quot;bluesky&quot;&lt;&#x2F;code&gt; attribute and the post URL. Nothing talks to the network until the reader scrolls the section into view.&lt;&#x2F;p&gt;
&lt;p&gt;When it does, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ttsigg&#x2F;auxdev.net&#x2F;blob&#x2F;main&#x2F;static&#x2F;js&#x2F;comments-loader.js&quot;&gt;comments-loader.js&lt;&#x2F;a&gt; dynamically imports &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ttsigg&#x2F;auxdev.net&#x2F;blob&#x2F;main&#x2F;static&#x2F;js&#x2F;comments-bluesky.js&quot;&gt;comments-bluesky.js&lt;&#x2F;a&gt;, which does a two-call dance:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;com.atproto.identity.resolveHandle&lt;&#x2F;code&gt; to turn the handle into a DID. Cached in &lt;code&gt;localStorage&lt;&#x2F;code&gt; keyed by handle, so repeat visits to posts from the same author skip this step.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;app.bsky.feed.getPostThread&lt;&#x2F;code&gt; to pull the post, its replies, and the replies-to-replies. Rendered as a nested tree.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The endpoints are the official &lt;code&gt;public.api.bsky.app&lt;&#x2F;code&gt; appview. No PDS auth required. If you rate-limit yourself by sitting on the post for an hour, hit refresh.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-gets-rendered&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-gets-rendered&quot; aria-label=&quot;Permalink to what-gets-rendered&quot;&gt;&lt;&#x2F;a&gt;
What gets rendered&lt;&#x2F;h2&gt;
&lt;p&gt;Reply text, author display name and handle, like and repost counts, relative timestamp. Links inside replies get &lt;code&gt;rel=&quot;external nofollow&quot;&lt;&#x2F;code&gt;. Avatars load from &lt;code&gt;cdn.bsky.app&lt;&#x2F;code&gt; with &lt;code&gt;loading=&quot;lazy&quot;&lt;&#x2F;code&gt;. Text content skips the sanitiser because the AT Protocol returns replies as plain text, not HTML. One of the small design calls the Bluesky team got right.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cost&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#cost&quot; aria-label=&quot;Permalink to cost&quot;&gt;&lt;&#x2F;a&gt;
Cost&lt;&#x2F;h2&gt;
&lt;p&gt;Loader: about 300 bytes. Renderer: about 3kb. No sanitiser needed. Avatars are the heaviest element and they lazy-load. On a post with ten replies, the cost of enabling comments is well under 10kb, and only after the reader scrolls to it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;sister-post&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#sister-post&quot; aria-label=&quot;Permalink to sister-post&quot;&gt;&lt;&#x2F;a&gt;
Sister post&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a href=&quot;&#x2F;blog&#x2F;comments-on-mastodon&#x2F;&quot;&gt;Mastodon comments demo&lt;&#x2F;a&gt; shows the same flow against the Mastodon public API. Both renderers share the loader and the DOM shape, so the same CSS applies.&lt;&#x2F;p&gt;
&lt;p&gt;Scroll down. The Bluesky thread should appear momentarily.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>voron-trident</title>
        <published>2026-03-01T00:00:00+00:00</published>
        <updated>2026-03-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://auxdev.net/projects/voron-trident/"/>
        <id>https://auxdev.net/projects/voron-trident/</id>
        
        <content type="html" xml:base="https://auxdev.net/projects/voron-trident/">&lt;p&gt;A 300mm Voron Trident, second printer. Most of the mistakes from the first one are already in the past. The enclosure is what is stalling me at the moment, and the enclosure has been stalling me for three months.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-short-version&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#the-short-version&quot; aria-label=&quot;Permalink to the-short-version&quot;&gt;&lt;&#x2F;a&gt;
The short version&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;300mm frame, Trident over 2.4 because z-motion is simpler and the bed mass is forgiving at this size.&lt;&#x2F;li&gt;
&lt;li&gt;Stealthburner toolhead on a Nitehawk SB2040 CAN bus board, so cables down to the carriage are few and skinny.&lt;&#x2F;li&gt;
&lt;li&gt;Manta M8P controller running Klipper, Mainsail for the UI.&lt;&#x2F;li&gt;
&lt;li&gt;Bed mesh + pressure advance + input shaper calibrated from a known-good starting point rather than from the defaults.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-is-still-open&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#what-is-still-open&quot; aria-label=&quot;Permalink to what-is-still-open&quot;&gt;&lt;&#x2F;a&gt;
What is still open&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Enclosure side panels. I have the parts. I do not have the weekend.&lt;&#x2F;li&gt;
&lt;li&gt;Chamber thermistor wiring. Easy job, keeps slipping.&lt;&#x2F;li&gt;
&lt;li&gt;A sensible filament runout sensor that does not false-positive on flexible filament.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;longer-write-up&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#longer-write-up&quot; aria-label=&quot;Permalink to longer-write-up&quot;&gt;&lt;&#x2F;a&gt;
Longer write-up&lt;&#x2F;h2&gt;
&lt;p&gt;A proper build log lives on the &lt;a href=&quot;&#x2F;blog&#x2F;voron-trident-build-log&#x2F;&quot;&gt;blog&lt;&#x2F;a&gt;. That post also covers what I would change on a hypothetical third printer, assuming enough years pass for me to forget the first two.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Mavolas</title>
        <published>2018-01-21T00:00:00+00:00</published>
        <updated>2018-01-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://auxdev.net/projects/crypto-wallet/"/>
        <id>https://auxdev.net/projects/crypto-wallet/</id>
        
        <content type="html" xml:base="https://auxdev.net/projects/crypto-wallet/">&lt;h2 id=&quot;mavolas&quot;&gt;
&lt;a class=&quot;post-anchor&quot; href=&quot;#mavolas&quot; aria-label=&quot;Permalink to mavolas&quot;&gt;&lt;&#x2F;a&gt;
Mavolas&lt;&#x2F;h2&gt;
&lt;p&gt;A cryptocurrency hardware wallet we were prototyping. Goal: something the size of a car remote. The 3d-printed prototype was the size of three cell phones stacked together, which is fine for a prototype.&lt;&#x2F;p&gt;
&lt;p&gt;It talked to our Bitcoin node over a built-in LTE modem. Battery life was over 4 days with the screen off. We used the Particle Electron board, got a decent working prototype, then didn&#x27;t pursue it further. Crypto adoption was the bottleneck, not the hardware.&lt;&#x2F;p&gt;
&lt;figure class=&quot;image-shortcode  image-left&quot;&gt;&lt;a class=&quot;image-lightbox-trigger&quot; href=&quot;&#x2F;projects&#x2F;crypto-wallet&#x2F;mavolasproto1.jpg&quot; data-lightbox=&quot;&#x2F;projects&#x2F;crypto-wallet&#x2F;mavolasproto1.jpg&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;auxdev.net&#x2F;processed_images&#x2F;mavolasproto1.9eed69055354baff.webp&quot; srcset=&quot;https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;mavolasproto1.713f9b39dfc505d4.webp 100w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;mavolasproto1.9eed69055354baff.webp 200w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;mavolasproto1.20fff3c6122fedfa.webp 400w, https:&amp;#x2F;&amp;#x2F;auxdev.net&amp;#x2F;processed_images&amp;#x2F;mavolasproto1.b1dca7e93b6800e9.webp 600w&quot; sizes=&quot;(max-width: 200px) 100vw, 200px&quot; width=&quot;200&quot; height=&quot;266&quot; alt=&quot;The size of the handheld&quot;
    loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;&#x2F;a&gt;&lt;figcaption&gt;The handheld.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
</content>
        
    </entry>
</feed>
