I published three posts on this blog just in April. Before that, I’ve published 3 posts since May 2020. Going back to freelance work and having an interesting bioinformatics project made me want to write again, and it feels good. But this blog had to feel a bit nicer first! This site has been around since 20161, and it was always just Markdown files in a git repo. Let’s update the toolchain around it as a little spring cleaning for 2026.

Hugo

The blog ran on Jekyll from its inception until last month. I chose it because GitHub Pages shipped it by default and I wanted to publish something. That worked in 2016, and I’m glad it’s actually still supported today. But, in 2026, getting Jekyll to build locally means fighting bundler, native extensions, and a Ruby toolchain I haven’t used for anything else in years. I could publish blindly and just see what I get out of it, but that’s no fun.

So, I looked for something else: Stable, usable, and likely to be maintained for the next years. Hugo looked like a great option. It’s a single binary, one brew install away. You run it, and the site builds in about 60 milliseconds. The expected content is Markdown with YAML frontmatter, so that I already had. If I move to something else in 2036, I expect a similar afternoon of find-and-replace.

Of course, I also wanted to make my own theme. At first I copy-pasted the one I had in Jekyll but I knew I wanted to take it further. Hugo’s templating language is Go templates. They have a pipe syntax that looks clean at first, but some functions don’t compose well in pipelines so you end up wrapping things in parentheses.2 It gets the job done.

A nice thing Hugo has is render hooks that let you customize how individual Markdown elements get rendered (e.g., links, images, code blocks) without touching the main templates. I wish they went further, though. The table of contents, for example, is generated as a blob of HTML with no hook to customize its structure.3

The theme

The design is loosly based on a personal website I had around 2015. That one had sidenotes, a serif font, and a lot of whitespace. When I started the new theme, I went for something quite clean and polished, with a lot of focs on typography. I now use Piazzolla , a really nice serif font4.

Then, my wife looked at it and said the older one was better. More nerdy and authentic, less magazine. She was right. What made the old site feel like mine was that the rendered HTML looked a bit like the Markdown source and was basically just about the content, in full monospace glory.

Markdown style

So I combined the two. I added a CSS @layer markdown-look: Headings get prefixed with ##, inline code gets wrapped in backtick markers, lists use instead of bullets, horizontal rules render as ---. and footnotes get [^x] styling.

h2::before {
  content: "## " / "";
  color: var(--md-marker);
}

code::before,
code::after {
  content: "`" / "";
  color: var(--md-marker);
}

/* ... */

Neat little bonus: The / "" in the content value is an alternative text so screen readers don’t announce the decoration.

Sidenotes

I write a lot of footnotes5 and having them at the bottom of the page always felt too far away. Parentheses are too noisy. Tufte-style sidenotes sit right next to the text, which is where you want the context.

On wide viewports (72rem and up), footnotes move into the right margin. Links that have title attribute also get pulled into the margin as annotations, showing the domain and the title text. The table of contents sticks to the left. And on narrow screens, everything is just one column.

The implementation is about 60 lines of JavaScript. It runs before first paint and clones both Hugo’s footnote content and links with a title attribute into <span> elements next to each reference and floats them into the margin.

The CSS is actually quite simple and has been done many times before. It’s just more fun with CSS features from 2026.

.sidenote {
  float: right;
  clear: right;
  width: var(--sidenote-width, 12rem);
  /* The negative margin pulls them out of the content column.  */
  margin-right: calc(
    -1 * (var(--sidenote-width, 12rem) + var(--sidenote-gap, 2rem))
  );
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

Oh, and there’s a also a .wide class to make tables and some code blocks easier to read.

Colors and dark mode

On big gap in the old blog design was that it was just black and white and pink links. Now, all colors are in oklch , a pretty neat color space that works well when adjusting lightness and blending colors. My entire accent palette actually comes from one (pink) --hue variable. Super satisfying that I can do this directly in code and get nice colors from some math.

Dark mode (a new feature) follows prefers-color-scheme by default with a [data-theme] attribute for manual override (currently unused). Both themes use the same token names, different oklch values. color-mix() handles the subtler bits, like blending the accent with transparency for link underlines:

a {
  text-decoration-color:
    color-mix(in oklch, var(--accent) 40%, transparent);
}

There are a few other 2025/2026 CSS features in here that I’m happy to finally use: text-wrap: balance on headings to avoid orphaned words, text-wrap: pretty on body text, and scroll-state() container queries6 for showing the table of contents header only when it’s stuck to the top of the viewport.

Diagrams

For my recent posts I also felt the urge to include some diagrams. I’ve gotten used to adding Mermaid diagrams to markdown files and this blog feels no different. Adding diagrams via code blocks with annotations also means the source stays just text and people are you these a lot so I guess they will stay around.

I didn’t want to include Mermaid’s client-side JS rendering library since it’s quite big and also won’t work in feed readers. So I was happy to see kroki.io , which provides a public API that you can post text diagram formats to (incl. Mermaid, GraphViz, and even Vega) and get SVGs back. With a bit of config, you can call HTTP endpoints from Hugo templates: A match made in heaven!

With this, we have diagrams as SVGs directly embedded in the rendered post pages. But they come with their own styles (at least the Mermaid ones). No worries. With a little bit of !important CSS styling, I overwrote the colors and fonts so its looks more “native” to the blog and also works in dark mode.

I wasnt’t sure how to demostrate this but here we go:

KrokiHugoPascalKrokiHugoPascalloop[every mermaidblock]Markdown postplease renderenjoy SVGHTML

Open Social Stuff

I like the idea of having a simple website that serves content directly on a domain that I own. Feel nicer than to publish on Medium7, dev.to, Substack, or some social media channel.

This blog has an RSS feed, and as someone who uses a feedreader daily, this is important to me. No need to visit this website if you want to read my content.

Publishing on the “ATmosphere”8 was quite simple. I set up an account for this blog (on Eurosky ) and then used Sequoia which syncs the blog content with it. It was very easy! You can now follow this blog here on Bluesky.

One more thing I used from Sequoia is their comments feature. Since the Bluesky API is public9, Sequoia comes with a little Web Component that allows showing all Bluesky replies just like comments. (I also added support for showing quote posts.) This means that the best way to reply to my blog posts is now to reply on Bluesky.

I also looked into publishing the content as an ActivityPub account, but in contrast to some tutorials I’ve seen it doesn’t really work with a static site. Looks like I need a slightly more dynamic setup to make it work10.

More writing

It’s fun to write! Maybe right now I’m a bit obsessed with putting on my thoughts into text after not doing it much for a while, but my hope is that this will last for a while. I’ve always been keen on clarifying my thoughts by phrasing them out and I read a lot of posts from other people every day.

Let me know what you’d like me to write more about and what you thought of this and my recent posts!


  1. Some of the content is even older, imported from a previous site. ↩︎

  2. For example, {{ .Title | truncate 50 }} reads naturally. But conditionally wrapping output requires nesting {{ if }} blocks or calling printf with parenthesized arguments instead of piping. Not awful, just occasionally surprising. ↩︎

  3. Unless I missed something. You can set startLevel and endLevel in the config, and that’s about it. I’d love to be able to control the markup or wrap individual entries. ↩︎

  4. By Juan Pablo del Peral at Huerta Tipográfica , who also made Alegreya , which I used before. ↩︎

  5. tangents, caveats, small jokes, stuff too long to put in parantheses, just like this one here ↩︎

  6. @container scroll-state(stuck: top) landed in Chrome 133 and Safari 18.4 and as of May 2026 doesn’t work in Firefox. ↩︎

  7. Is that still a thing people use? ↩︎

  8. This is what Bluesky is built on, the AT protocol. ↩︎

  9. Like APIs were in the good old days! ↩︎

  10. This blog is currently hosted on Cloudflare, so dymanic ActivityPub stuff as well as hosting my own PDS for AT would be both doable , but outside the “everything is Markdown files” realm, so I didn’t do anything for this yet. ↩︎