{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreie6ttca2pz4xhmeylukl4sdeiefnx6hogy7xtgxkpymgcjfvl54iq",
"uri": "at://did:plc:3okajqf5ifpzwzkuzluxd66m/app.bsky.feed.post/3mk6lygnkipq2"
},
"path": "/still-blogging.html",
"publishedAt": "2026-04-22T22:00:00.000Z",
"site": "https://deterministic.space",
"tags": [
"bioinformatics project",
"Jekyll",
"Hugo",
"render hooks",
"Piazzolla",
"alternative text",
"Tufte-style",
"oklch",
"Mermaid",
"kroki.io",
"Eurosky",
"Sequoia",
"here",
"Huerta Tipográfica",
"Alegreya",
"Bluesky",
"both",
"doable",
"@layer",
"@container"
],
"textContent": "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.\n\n## Hugo\n\nThe 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.\n\nSo, 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.\n\nOf 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.\n\nA 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\n\n## The theme\n\nThe 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.\n\nThen, 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.\n\n### Markdown style\n\nSo 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.\n\n\n h2::before {\n content: \"## \" / \"\";\n color: var(--md-marker);\n }\n\n code::before,\n code::after {\n content: \"`\" / \"\";\n color: var(--md-marker);\n }\n\n /* ... */\n\n\n_Neat little bonus:_ The `/ \"\"` in the `content` value is an alternative text so screen readers don’t announce the decoration.\n\n### Sidenotes\n\nI 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.\n\nOn 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.\n\nThe 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.\n\nThe CSS is actually quite simple and has been done many times before. It’s just more fun with CSS features from 2026.\n\n\n .sidenote {\n float: right;\n clear: right;\n width: var(--sidenote-width, 12rem);\n /* The negative margin pulls them out of the content column. */\n margin-right: calc(\n -1 * (var(--sidenote-width, 12rem) + var(--sidenote-gap, 2rem))\n );\n font-size: var(--text-xs);\n color: var(--text-secondary);\n }\n\n\nOh, and there’s a also a `.wide` class to make tables and some code blocks easier to read.\n\n### Colors and dark mode\n\nOn 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.\n\nDark 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:\n\n\n a {\n text-decoration-color:\n color-mix(in oklch, var(--accent) 40%, transparent);\n }\n\n\nThere 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.\n\n### Diagrams\n\nFor 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.\n\nI 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!\n\nWith 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.\n\nI wasnt’t sure how to demostrate this but here we go:\n\nKrokiHugoPascalKrokiHugoPascal>>>>loop[every mermaidblock]Markdown postplease renderenjoy SVGHTML\n\n## Open Social Stuff\n\nI 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.\n\nThis 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.\n\nPublishing 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.\n\nOne 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.\n\nI 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.\n\n## More writing\n\nIt’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.\n\nLet me know what you’d like me to write more about and what you thought of this and my recent posts!\n\n* * *\n\n 1. Some of the content is even older, imported from a previous site. ↩︎\n\n 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. ↩︎\n\n 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. ↩︎\n\n 4. By Juan Pablo del Peral at Huerta Tipográfica , who also made Alegreya , which I used before. ↩︎\n\n 5. tangents, caveats, small jokes, stuff too long to put in parantheses, just like this one here ↩︎\n\n 6. `@container scroll-state(stuck: top)` landed in Chrome 133 and Safari 18.4 and as of May 2026 doesn’t work in Firefox. ↩︎\n\n 7. Is that still a thing people use? ↩︎\n\n 8. This is what Bluesky is built on, the AT protocol. ↩︎\n\n 9. Like APIs were in the good old days! ↩︎\n\n 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. ↩︎\n\n\n",
"title": "Still blogging"
}