<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wc="https://wilhelm.codes/ns/feed/1.0"><channel><title>Wilhelm Codes · Tailwindcss</title><link>https://wilhelm.codes/tags/tailwindcss/</link><description>Slinger of nibbles, bits and bytes. Over 25 years of professional experience as a software engineer. Love making glowing rectangles go "beep-boop".</description><generator>Hugo</generator><language>en-US</language><managingEditor>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</managingEditor><webMaster>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</webMaster><lastBuildDate>Thu, 18 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://wilhelm.codes/tags/tailwindcss/index.xml" rel="self" type="application/rss+xml"/><item><title>Let Me Show You My Bits</title><link>https://wilhelm.codes/blog/let-me-show-you-my-bits/</link><pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/let-me-show-you-my-bits/</guid><category>hugo</category><category>css</category><category>tailwindcss</category><category>wilhelm.codes</category><wc:kind>post</wc:kind><description>A while back, in Reading Between the Posts, I wired up the home page so the quiet gaps between articles would narrate themselves - &amp;ldquo;1 bit and 41 changelog events in between&amp;rdquo;. There was just one tiny problem with that sentence. At the time I had written exactly zero bits.</description><content:encoded><![CDATA[<p>A while back, in <a href="https://wilhelm.codes/blog/reading-between-the-posts/">Reading Between the Posts</a>, I wired up the home page so the quiet gaps between articles would narrate themselves - &ldquo;<em>1 bit and 41 changelog events in between</em>&rdquo;. There was just one tiny problem with that sentence. At the time I had written exactly <em>zero</em> bits.</p>
<p>So let me finally show you my bits.</p>
<h2 id="what-even-is-a-bit">
  <a class="heading-link" href="#what-even-is-a-bit">What even is a bit?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>A bit is the smallest unit of &ldquo;I made a thing&rdquo; I&rsquo;m willing to commit to. It could be a quote that stuck with me, a link worth keeping, the odd YouTube video, or a one-line thought too small to earn its own long-form entry. Each one is a little markdown file in <code>/bits</code> and  the only thing it really has to declare is what <em>kind</em> of bit it is:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">YAML</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">quote</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2026-06-17</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Edsger Dijkstra&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;Simplicity is a great virtue but it requires hard work to achieve it.&#34;</span></span></span></code></pre></div>
</div>
<p><code>type</code> is the whole trick that everything else hangs off.</p>
<h2 id="one-shape-per-thought">
  <a class="heading-link" href="#one-shape-per-thought">One shape per thought.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>The thing I didn&rsquo;t want was for a pithy quote to render like a bare link or to render like a video. They&rsquo;re different shapes of thought, so they ought to look different.</p>
<p>I&rsquo;d already learned this lesson building the <a href="https://wilhelm.codes/blog/a-changelog-that-builds-itself/">changelog</a>, which leans on one tiny partial per event type. So I stole from myself. There&rsquo;s a single lookup table mapping each <code>type</code> to an icon, an accent colour and a label:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Go</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">meta</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">dict</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#34;quote&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;icon&#34;</span><span class="w"> </span><span class="s">&#34;quote-right&#34;</span><span class="w">    </span><span class="s">&#34;colour&#34;</span><span class="w"> </span><span class="s">&#34;rose&#34;</span><span class="w">    </span><span class="s">&#34;label&#34;</span><span class="w"> </span><span class="s">&#34;quote&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#34;link&#34;</span><span class="w">  </span><span class="p">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;icon&#34;</span><span class="w"> </span><span class="s">&#34;arrow-up-right&#34;</span><span class="w"> </span><span class="s">&#34;colour&#34;</span><span class="w"> </span><span class="s">&#34;blue&#34;</span><span class="w">    </span><span class="s">&#34;label&#34;</span><span class="w"> </span><span class="s">&#34;link&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#34;video&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;icon&#34;</span><span class="w"> </span><span class="s">&#34;youtube&#34;</span><span class="w">        </span><span class="s">&#34;colour&#34;</span><span class="w"> </span><span class="s">&#34;red&#34;</span><span class="w">     </span><span class="s">&#34;label&#34;</span><span class="w"> </span><span class="s">&#34;video&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#34;blurb&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;icon&#34;</span><span class="w"> </span><span class="s">&#34;pen-nib&#34;</span><span class="w">        </span><span class="s">&#34;colour&#34;</span><span class="w"> </span><span class="s">&#34;emerald&#34;</span><span class="w"> </span><span class="s">&#34;label&#34;</span><span class="w"> </span><span class="s">&#34;blurb&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}}</span></span></span></code></pre></div>
</div>
<p>It then matches a micro-partial per type that decides how the content actually renders. Adding a brand new kind of bit is now &ldquo;add one row, add one tiny template&rdquo;, which is about as many steps as I&rsquo;m willing to tolerate.</p>
<p><img
  src="https://wilhelm.codes/blog/let-me-show-you-my-bits/bits_hu_88740d4c34822745.webp"
  srcset="/blog/let-me-show-you-my-bits/bits_hu_88740d4c34822745.webp 736w, /blog/let-me-show-you-my-bits/bits_hu_7c64b843d8098172.webp 1104w, /blog/let-me-show-you-my-bits/bits_hu_321d663198ba2c25.webp 1472w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="596"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h2 id="hugo-why-are-you-like-this">
  <a class="heading-link" href="#hugo-why-are-you-like-this">Hugo, why are you like this?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>My bits started life with two perfectly reasonable front-matter fields: <code>type</code> and <code>url</code>. Hugo took one look and quietly robbed me of both.</p>
<p>It turns out <code>url</code> is a <em>reserved</em> key which Hugo uses to treat as the page&rsquo;s permalink. So, the moment I put a <code>https://...</code> in there, the build fell over with a stern little message about unsupported protocols. <code>type</code> doesn&rsquo;t even have the decency to error. It silently moves out of <code>.Params.type</code> and into <code>.Type</code>, which <em>also</em> hijacks the layout lookup. So my <code>.Params.type</code> was resolving to nothing and every single bit was quietly rendering as the fallback.</p>
<p>Turns out hte fix for this was simply to rename <code>url</code> to <code>link</code> and  read <code>.Type</code> instead of <code>.Params.type</code>. Half an hour of my life dedicated to two words I didn&rsquo;t know were already spoken for.</p>
<p>Coding is my passion.</p>
<h2 id="bits-flapping-in-the-wind">
  <a class="heading-link" href="#bits-flapping-in-the-wind">Bits flapping in the wind.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Rather than dumping bits into the same fold-out list as the GitHub noise, I pulled them out as their own first-class timeline rows. The lower-signal changelog chatter stays tucked behind the &ldquo;<em>N changelog events in between</em>&rdquo; disclosure. So, the home page now reads in three tiers:</p>
<ul>
<li>the loud headline articles</li>
<li>the medium-volume bits</li>
<li>the quiet changelog hum</li>
</ul>
<p>The same interstitial machinery I built last time, but bits just get to stand a little taller than a commit.</p>
<p><img
  src="https://wilhelm.codes/blog/let-me-show-you-my-bits/home_hu_e7f8fe2d00bce999.webp"
  srcset="/blog/let-me-show-you-my-bits/home_hu_e7f8fe2d00bce999.webp 736w, /blog/let-me-show-you-my-bits/home_hu_bf5b90ae24a2b65.webp 1104w, /blog/let-me-show-you-my-bits/home_hu_62fda724f7b72c05.webp 1472w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="310"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h2 id="the-tag-that-went-nowhere">
  <a class="heading-link" href="#the-tag-that-went-nowhere">The tag that went nowhere.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Bits are deliberately <code>render: never</code> in Hugo. They live inline on the bits page and never get their own URL. Except &ldquo;no page&rdquo; also quietly means &ldquo;no place in the taxonomy&rdquo;, which means a tag only ever used by a bit has no <code>/tags/...</code> page to point at. If I link it anyway I&rsquo;ve shipped a <code>404</code>.</p>
<p>So, the tags only become links if there&rsquo;s actually somewhere to go:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Go</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="nx">site</span><span class="p">.</span><span class="nf">GetPage</span><span class="w"> </span><span class="p">(</span><span class="nx">printf</span><span class="w"> </span><span class="s">&#34;/tags/%s&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">urlize</span><span class="w"> </span><span class="p">.))</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">&lt;</span><span class="nx">a</span><span class="w"> </span><span class="nx">href</span><span class="p">=</span><span class="s">&#34;{{ .RelPermalink }}&#34;</span><span class="p">&gt;</span><span class="err">#</span><span class="p">{{</span><span class="w"> </span><span class="p">.</span><span class="nx">Data</span><span class="p">.</span><span class="nx">Term</span><span class="w"> </span><span class="p">}}&lt;</span><span class="o">/</span><span class="nx">a</span><span class="p">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">&lt;</span><span class="nx">span</span><span class="p">&gt;</span><span class="err">#</span><span class="p">{{</span><span class="w"> </span><span class="p">.</span><span class="w"> </span><span class="p">}}&lt;</span><span class="o">/</span><span class="nx">span</span><span class="p">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span></span></span></code></pre></div>
</div>
<p>A tag with a real page becomes a link and a bit-only tag stays as plain text. No broken links and the nice part is it heals itself the moment a proper article picks up the same tag.</p>
<h2 id="in-closing-">
  <a class="heading-link" href="#in-closing-">In closing &hellip;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>The front page has quietly turned into a proper little stream. Long-form when I&rsquo;ve actually got something to say, bits when I don&rsquo;t and  a <a href="https://wilhelm.codes/blog/a-year-in-circles/">year of squircles</a> up top keeping a tally of it all.</p>
<p>Which is the entire point. The less effort it takes to post a half-formed thought, the more likely I am to actually post it. And the more I post, the more those gaps fill in.</p>
<p>And, as always, you can check everything out for yourself in the <a href="https://github.com/wilhelm-murdoch/wilhelm.codes">repo</a>.</p>]]></content:encoded></item><item><title>A Year in Circles... I mean Squares. Or, is it Squircles?</title><link>https://wilhelm.codes/blog/a-year-in-circles/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/a-year-in-circles/</guid><category>hugo</category><category>css</category><category>tailwindcss</category><wc:kind>post</wc:kind><description>If you scroll up to the top of the home page, you&amp;rsquo;ll find a pair of little rows of green squircles. It&amp;rsquo;s my own dumbed-down take on GitHub&amp;rsquo;s contribution graph. Effectively, it&amp;rsquo;s the same idea, except instead of a year of days it&amp;rsquo;s a year of weeks. Two rows of twenty-six with one squircle per week, with each squircle coloured a deeper shade of green the busier that week was.</description><content:encoded><![CDATA[<p>If you scroll up to the top of the home page, you&rsquo;ll find a pair of little rows of green squircles. It&rsquo;s my own dumbed-down take on GitHub&rsquo;s contribution graph. Effectively, it&rsquo;s the same idea, except instead of a year of days it&rsquo;s a year of <em>weeks</em>. Two rows of twenty-six with one squircle per week, with each squircle coloured a deeper shade of green the busier that week was.</p>
<p>I wanted something that summed up &ldquo;Has Wilhelm actually been doing anything lately?&rdquo; at a glance, without the density of 365 tiny day cells. Fifty-two squircles felt about right. Coarse enough to read across the room, fine enough to still give a picture.</p>
<h2 id="what-counts-as-a-weeks-worth-of-activity">
  <a class="heading-link" href="#what-counts-as-a-weeks-worth-of-activity">What counts as a &ldquo;week&rsquo;s worth of activity&rdquo;?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>The short answer is <em>everything</em>. The <a href="https://wilhelm.codes/blog/a-changelog-that-builds-itself/">changelog</a> already pulls my GitHub activity at build time, so I reuse that exact same data. Then, I throw in the things GitHub doesn&rsquo;t know about; every type of blog post on this site.</p>
<p>Because the changelog fetching already lives in a tidy little partial, sourcing the everything is just a matter of asking for each one and collecting all the publish dates:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Go</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">dates</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">slice</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;changelog/fetch.html&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;url&#34;</span><span class="w"> </span><span class="err">$</span><span class="nx">commitsUrl</span><span class="w"> </span><span class="s">&#34;fixture&#34;</span><span class="w"> </span><span class="err">$</span><span class="nx">fixture</span><span class="p">)</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">dates</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="err">$</span><span class="nx">dates</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">append</span><span class="w"> </span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">AsTime</span><span class="w"> </span><span class="p">.</span><span class="nx">commit</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">date</span><span class="p">)</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">where</span><span class="w"> </span><span class="nx">site</span><span class="p">.</span><span class="nx">RegularPages</span><span class="w"> </span><span class="s">&#34;Section&#34;</span><span class="w"> </span><span class="s">&#34;blog&#34;</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">dates</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="err">$</span><span class="nx">dates</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">append</span><span class="w"> </span><span class="p">.</span><span class="nx">Date</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span></span></span></code></pre></div>
</div>
<p>Nothing crazy going on here. I&rsquo;m just creating a big list of timestamps from wherever I happen to leave a trail.</p>
<h2 id="sorting-a-pile-of-dates-into-weekly-buckets">
  <a class="heading-link" href="#sorting-a-pile-of-dates-into-weekly-buckets">Sorting a pile of dates into weekly buckets.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Each of these dates now need to land in one of fifty-two buckets. Working out which week a given date belongs to is just some boring epoch arithmetic; how many whole weeks ago was it?</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Go</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">ago</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="nf">div</span><span class="w"> </span><span class="p">(</span><span class="nx">sub</span><span class="w"> </span><span class="nx">now</span><span class="p">.</span><span class="nx">Unix</span><span class="w"> </span><span class="err">$</span><span class="nx">d</span><span class="p">.</span><span class="nx">Unix</span><span class="p">)</span><span class="w"> </span><span class="mi">604800</span><span class="p">)</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nf">and</span><span class="w"> </span><span class="p">(</span><span class="nx">ge</span><span class="w"> </span><span class="err">$</span><span class="nx">ago</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">lt</span><span class="w"> </span><span class="err">$</span><span class="nx">ago</span><span class="w"> </span><span class="mi">52</span><span class="p">)</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">counts</span><span class="p">.</span><span class="nf">Add</span><span class="w"> </span><span class="p">(</span><span class="nx">printf</span><span class="w"> </span><span class="s">&#34;%d&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">sub</span><span class="w"> </span><span class="mi">51</span><span class="w"> </span><span class="err">$</span><span class="nx">ago</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span></span></span></code></pre></div>
</div>
<p>That <code>604800</code> is the number of seconds in a week. And yes, I did have to look that up. Anything older than fifty-two weeks just falls off the back and is quietly ignored.</p>
<h2 id="painting-the-squircles">
  <a class="heading-link" href="#painting-the-squircles">Painting the squircles.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>My favourite part of this is there&rsquo;s no JS involved. The whole thing is a CSS grid of twenty-six columns, and because each squircle is <code>aspect-square</code>, the rows just work and the squircles stretch to fill whatever container I drop the partial into:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">HTML</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grid grid-cols-26 gap-1.5&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="c">&lt;!-- 52 of these green little dudes --&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;aspect-square rounded-full bg-emerald-400&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div>
</div>
<p>Working out the shade was the fiddly bit. My first attempt simply scaled each week against the busiest one, which sounds sensible right up until you remember my data is basically one enormous week and a whole lot of flat nothing. That single monster week hogged the dark end and squashed everything else into the same pale green. So I did what GitHub does and reached for <a href="https://en.wikipedia.org/wiki/Quartile">quartiles</a> instead. You rank the weeks that actually saw some activity, chop them into four groups, and let a week&rsquo;s colour come from where it lands in the pack rather than from some absolute number. Empty weeks stay a faint <code>emerald-100</code>; the rest climb through four steps of green up to <code>emerald-600</code>. The part I like is that it&rsquo;s relative to <em>me</em>; a busy week is only busy compared to my <em>other</em> weeks.</p>
<p>The little tooltip that pops up when you hover over a squircle is also pure CSS. A <code>group</code> on the wrapper, a <code>group-hover:opacity-100</code> on the popup, a <code>transition</code> is all I needed. Each squircle now gives a little <code>scale</code> on hover too, just because it&rsquo;s nice.</p>
<h2 id="the-graphs-painful-honesty">
  <a class="heading-link" href="#the-graphs-painful-honesty">The graph&rsquo;s painful honesty.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Now on to the more embarrassing part. When I first rendered it with real data, I got <em>one</em> lonely green squircle and fifty-one empty ones. Surely, you&rsquo;ve noticed it on the front page.</p>
<p>For the curious, this is what it <em>would</em> look like if I didn&rsquo;t have commitment issues.
<img
  src="https://wilhelm.codes/blog/a-year-in-circles/activity-graph-demo_hu_ea2ab57358bd9cb8.webp"
  srcset="/blog/a-year-in-circles/activity-graph-demo_hu_ea2ab57358bd9cb8.webp 736w, /blog/a-year-in-circles/activity-graph-demo_hu_ec221f404c400ffe.webp 1104w, /blog/a-year-in-circles/activity-graph-demo_hu_1cd3ec9be12d0e93.webp 1472w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="91"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>At the time I assumed I&rsquo;d done something wrong. But, to my great shame, I hadn&rsquo;t. Turns out that when you vanish from your own website for close to 18 months and then cram an <a href="https://wilhelm.codes/blog/some-long-overdue-housekeeping/">entire renovation</a> into a single week, the graph renders exactly that. A long, flat, pale stretch of road ending with one pathetic little green emerald.</p>
<p>There&rsquo;s an extra little indignity baked into the quartiles, too. They need a <em>spread</em> to rank against, and with exactly one active week there&rsquo;s nothing to compare it to. So my massive renovation-cramming week doesn&rsquo;t even get to be properly dark green. It turns up as a polite, middling shade.</p>
<p>I effectively built a little a tool whose entire job is to hold up a mirror to my own lack of consistency and commitment. Very cool! 😬👌</p>
<h2 id="drop-it-in-anywhere">
  <a class="heading-link" href="#drop-it-in-anywhere">Drop it in anywhere.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>This whole thing is a single self-contained partial. It was waaaay easier to build out than I had originally thought. And, thanks to Tailwind I didn&rsquo;t even have to fall back on any JS! I count that as a bonus.</p>
<p>For the moment, it&rsquo;ll live on the home page, but I can now just place this anywhere in my Hugo site and it&rsquo;ll work:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Go</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;activity-graph.html&#34;</span><span class="w"> </span><span class="p">.</span><span class="w"> </span><span class="p">}}</span></span></span></code></pre></div>
</div>
<p>I might even build it out a bit more to support different colour schemes or specificy types of targeted site content. So, the more I write, the more those squircles fill in.</p>
<p>Consider yourself warned, <em>me</em>.</p>]]></content:encoded></item><item><title>Some Long Overdue Housekeeping</title><link>https://wilhelm.codes/blog/some-long-overdue-housekeeping/</link><pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/some-long-overdue-housekeeping/</guid><category>hugo</category><category>tailwindcss</category><category>css</category><category>refactoring</category><category>wilhelm.codes</category><wc:kind>post</wc:kind><description>This blog has been quietly chugging along for a while now without me paying it much attention. Which is, I suppose, the whole point of a set-and-forget setup. But &amp;ldquo;set-and-forget&amp;rdquo; has a sneaky way of becoming &amp;ldquo;forgotten&amp;rdquo;, and the longer you leave something untouched, the more it quietly rots behind your back. So I rolled up my sleeves and gave the whole thing a proper tune-up.</description><content:encoded><![CDATA[<p>This blog has been quietly chugging along for a while now without me paying it much attention. Which is, I suppose, the whole point of a <a href="https://wilhelm.codes/blog/my-blog-publishing-setup/">set-and-forget</a> setup. But &ldquo;set-and-forget&rdquo; has a sneaky way of becoming &ldquo;forgotten&rdquo;, and the longer you leave something untouched, the more it quietly rots behind your back. So I rolled up my sleeves and gave the whole thing a proper tune-up.</p>
<h2 id="confession-time">
  <a class="heading-link" href="#confession-time">Confession Time<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>While poking around the build, I discovered something a little embarrassing. My Tailwind setup - the source, the config, and the <em>entire</em> <code>node_modules</code> directory - was living inside Hugo&rsquo;s <code>static/</code> folder.</p>
<p>If you know Hugo, you already know where this is going. Everything in <code>static/</code> gets copied, verbatim, into the final site. Which means I had been cheerfully publishing my whole build toolchain - megabytes of it - to the live site on every single deploy. So, yeah. Production was shipping <code>node_modules</code>. Coding is my passion.</p>
<p>Nobody noticed, nothing broke, and the world kept turning. But it&rsquo;s the kind of thing that, once you see it, you can&rsquo;t <em>un</em>-see. To be fair to myself, when I originally put this Hugo site together, I only learned enough to get something shipped.</p>
<h2 id="letting-hugo-do-the-heavy-lifting">
  <a class="heading-link" href="#letting-hugo-do-the-heavy-lifting">Letting Hugo do the heavy lifting.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>The reason that mess existed in the first place was that I&rsquo;d wired up Tailwind as a separate, manual build step that spat out a compiled stylesheet for Hugo to pick up. It worked, but it was a second moving part I had to remember existed.</p>
<p>The good news is that recent versions of Hugo can drive <a href="https://gohugo.io/functions/css/tailwindcss/">Tailwind</a> itself, natively, as part of the normal asset pipeline. Combined with Tailwind v4 - which finally ditches the JavaScript config file in favour of configuring everything in CSS - I got to delete a <em>lot</em> of stuff:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git diff --shortstat main...modernize
</span></span><span class="line"><span class="cl"><span class="m">64</span> files changed, <span class="m">1872</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">40197</span> deletions<span class="o">(</span>-<span class="o">)</span></span></span></code></pre></div>
</div>
<p>The whole stylesheet now starts its life as a single entry point:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">CSS</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">@</span><span class="k">import</span> <span class="s2">&#34;tailwindcss&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">plugin</span> <span class="s2">&#34;@tailwindcss/typography&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">source</span> <span class="s2">&#34;hugo_stats.json&#34;</span><span class="p">;</span></span></span></code></pre></div>
</div>
<p>That <code>hugo_stats.json</code> bit is the clever part. Hugo writes out a list of every utility class it actually emits, and Tailwind reads <em>that</em> to decide what to generate. No more pointing Tailwind at my templates and hoping it guesses right.</p>
<p>Then a small partial hands it all off to Hugo to compile, minify and fingerprint:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Go</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nf">with</span><span class="w"> </span><span class="p">(</span><span class="nx">templates</span><span class="p">.</span><span class="nf">Defer</span><span class="w"> </span><span class="p">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;key&#34;</span><span class="w"> </span><span class="s">&#34;css&#34;</span><span class="p">))</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">Get</span><span class="w"> </span><span class="s">&#34;css/main.css&#34;</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="err">$</span><span class="nx">opts</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;minify&#34;</span><span class="w"> </span><span class="p">(</span><span class="nx">not</span><span class="w"> </span><span class="nx">hugo</span><span class="p">.</span><span class="nx">IsDevelopment</span><span class="p">)</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="p">.</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">css</span><span class="p">.</span><span class="nx">TailwindCSS</span><span class="w"> </span><span class="err">$</span><span class="nx">opts</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="cm">/* ...do very cool things... */</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="p">}}</span></span></span></code></pre></div>
</div>
<p>The <code>templates.Defer</code> wrapper is there because the CSS can&rsquo;t compiled ( transpiled? ) until Hugo has finished rendering every page and knows the full list of classes. So, in a very real way, Hugo solves an annoying 🐔 and 🥚 problem.</p>
<p>This means no more standalone Tailwind config, no committed stylesheet, no <code>node_modules</code> in <code>static/</code>, and no build toolchain leaking onto the live site. Very cool!</p>
<h2 id="so-many-deprecations">
  <a class="heading-link" href="#so-many-deprecations">So. Many. Deprecations.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Of course, nothing that&rsquo;s been left alone for a year comes back to life cleanly. Bumping Hugo to the latest release lit up the console like a Christmas tree.</p>
<p>A few of my templates were leaning on things that have since been politely shown the door:</p>
<ul>
<li><code>resources.GetRemote ... .Err</code> for the <a href="https://wilhelm.codes/changelog/">changelog page</a> - that pattern was removed in favour of a shiny new <code>try</code> keyword.</li>
<li><code>.Language.LanguageCode</code> and <code>.Language.LanguageDirection</code>, both deprecated in favour of <code>.Locale</code> and <code>.Direction</code>.</li>
<li>The <code>_build</code> and <code>cascade._target</code> front matter keys, now just <code>build</code> and <code>cascade.target</code>.</li>
</ul>
<p>None of it was hard to fix, but it&rsquo;s a good reminder that &ldquo;it still builds&rdquo; and &ldquo;it builds <em>without complaints</em>&rdquo; are two very different bars.</p>
<h2 id="a-footgun">
  <a class="heading-link" href="#a-footgun">A Footgun!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Here&rsquo;s a fun one. The changelog page pulls in a Github event fixture file during local development by fetching it over <code>http://localhost:1313</code>. Effectively, from the very dev server that&rsquo;s <em>trying to build the page</em>.</p>
<p>Those of you who are familiar with such things can probably see the problem. Hugo builds the site <em>before</em> it starts listening on that port, so the build sits there waiting for a server that doesn&rsquo;t exist yet. A deadlock of my own making. The obvious fix was to just read the file off disk instead of asking the network nicely.</p>
<h2 id="some-honourable-mentions">
  <a class="heading-link" href="#some-honourable-mentions">Some honourable mentions.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>A grab-bag of smaller wins while I had the hood up:</p>
<ul>
<li>Fonts are now served as <code>woff2</code> instead of raw <code>ttf</code>, with <code>font-display: swap</code> so text shows up immediately instead of hanging around invisible. This alone shaved the font payload down by about 60%.</li>
<li>I also deleted a few MB of fluff that wasn&rsquo;t being loaded by anything.</li>
<li>Prettier got a nice version bump and I taught it to sort my Tailwind classes, so I can stop pretending I do that consistently by hand.</li>
</ul>
<h2 id="was-the-juice-worth-the-squeeze">
  <a class="heading-link" href="#was-the-juice-worth-the-squeeze">Was the juice worth the squeeze?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Aside from a few cosmetic updates here and there, the site should look pretty much the same as it did before. Which is, weirdly, the whole point; all of this work was about the parts you can&rsquo;t see. I got a leaner build, faster page loads, and a project I can come back to in another year without wincing; famous last words, etc&hellip;</p>
<p>For now, the house is clean. Very nice.</p>]]></content:encoded></item></channel></rss>