A new website

I finally finished my redesign and redevelopment of my site, and here it is. I’ve replaced a hacked‐together and powerful mess of Hyde which I had for six years with a differently‐hacked‐together structure of Zola.

My website used to look like this. I designed that site and I designed this site.

I’ve actually gone through one other reimplementation and three other redesigns since that time, none of which I published.

I had a Lektor site complete in August 2016, but I didn’t publish it at the time because (a) I was experimenting with another completely different style, and (b) there was one fairly trivial issue with the implementation of the design which I was never quite happy with (it pertained to the appearance of side‐by‐side code blocks, as seen in my FizzBuzz article, when it was too wide to wrap).

Then later I held off because I was contemplating rewriting it all in Rust. And I kept on delaying and delaying and did another two complete redesigns (one of which I’ve kept and may use on another project, one of which I threw away) and two partial redesigns (again one kept, one discarded). Eventually, after we’d taken the new Fastmail website live with Zola I decided I just had to finish it off, and started on yet another redesign, drawing threads from several of the intermediate redesigns. And out came this calf.

The source code is not publicly available at this time.

I want to remind people that CSS custom properties (variables) are really nice things for their ability to minimise the contents of media query blocks. For things like colours, using Sass variables or the likes can be effective, but for layout matters that change by size, CSS custom properties are much more powerful and effective.

Browser support

Firefox is the golden standard.

Chrome works well enough. No known bugs, after I stopped using mix-blend-mode extensively, since it turns out to be extremely buggy on Chrome. Chrome’s font rendering is also still as poor on Windows as ever. (I’m hoping that Edge’s transition to Chromium will see Microsoft implementing good native‐appearance font rendering for it and upstreaming it to Chromium.)

I have deliberate dropped support for Internet Explorer, because few use it and it would prevent my use of CSS Custom Properties to manage the layout. IE users will still get the content and some of the formatting, but the layout will be very wrong. I’m fine with that.

Edge gets a somewhat degraded experience due to not supporting #rgba colour syntax in CSS (and I’m disinclined to transform them to rgba()), and not implementing the <details> element (and I have decided not to polyfill it).

Safari? I haven’t tried it, but it should be fine.

All content should work properly on browsers of all reasonable sizes, down to at least 320px wide and generally further. Up to about 700px wide gets a compact layout, above that gets the full layout with a sidebar, where some sidenotes may reside and a few other layout elements.


The URL scheme has changed, e.g. from /blog/slug.html to /blog/slug/. But all existing links should continue to work, because I care. (This applies to public SVG and image paths too. Internal subresources like CSS are deliberately gone.)

The blog Atom feed has moved from /blog.xml to /feed.xml (with redirect) in part for convenience with Zola and in part due to future plans I have. I kept all existing entry IDs intact, so feed readers shouldn’t show all the old posts again.

Post images: in Hyde I supported this properly, as something that would go into Twitter cards and such. In practice, my Rust FizzBuzz article was the only one I had drawn such an image for. (Tween also got a fun little illustration at the end, but it’s not a post image.) In the Lektor design I doubled down on this and even drew a new illustration for the FizzBuzz article. But in the Zola design I found no good place for a post image, and so have discarded the concept. It’s probably for the best.

Some content didn’t translate well directly to the altered style; so I made some content formatting changes on old posts. (I never was interested in a simple, boring blog with no fancy markup. This always means more effort in the long run, yet I don’t regret it.)



I have a master stylesheet, a dark mode stylesheet that overrides various values from the main stylesheet.


I have maintained my general aversion to JavaScript, and include no third‐party resources. I use three pieces of JavaScript:

  1. The theme switcher widget in the top right hand corner, where you can switch between light mode and dark mode, and choose a sans‐serif font for body text instead of my default serif. See my notes about the dark mode implementation if you’re interested in that. (If you don’t have JavaScript enabled, it’ll still follow the preference the user has expressed to the browser.)

  2. To make <soft-br></soft-br> indicate a preferred line break point, reflowed on resize, iff it won’t increase the height of the containing paragraph. This is a subtle affair. I have employed it on my FizzBuzz article. Pages only include this JavaScript if it’s used.

  3. Matomo for analytics, self‐hosted so that I’m not loading any third‐party JS. (It was only in mid‐2018 that I migrated from Google Analytics to self‐hosted Matomo; until then I was aware of the folly of letting Google execute arbitrary JavaScript in your browser, but doing something about it was going to take too much effort.)

I have in mind to add a fourth piece of JavaScript, to manually finesse the vertical alignment of sidenotes in the full layout. Pure CSS float: left and clear: left gets you a long way, but it’s not perfect.


The fonts used now are by Matthew Butterick, of Practical Typography fame: body text in the serif Equity (Equity Text A)Don’t like serif body text? I implemented a switcher that lets you go sans‐serif, see the switcher in the top right corner if you have JavaScript enabled., headings and sidenotesLike this one! in the sans‐serif Concourse (T4 and T6), and code and other monospaced text in Triplicate (T4 and T6, Code and Poly, with Poly disabled for non‐code samples and the rare bit of code where visual alignment is performed). These have been my preferred fonts for the last couple of years, especially Triplicate for monospaced things.

Font delivery is not well-optimised at this time, and frankly probably never will be. I have a dozen font files totalling 183KB (as woff2), of which most pages with any code will use nine at 145KB. I’ve considered inlining at least these into the CSS file.

Rambly remarks on Lektor and Zola

Most static site generators give you a body field and a front‐matter block. That’s how it was in Hyde; and that’s how it is in Zola, with Zola having two models (sections and pages) and an eclectic mixture of predefined fields, half of which have no particular semantics (e.g. title, description), and then you can define whatever else you like in an extra table (and I do).

Lektor, on the other hand, let you fully define your own content models, and I rather liked that. I’d be interested in experimentation with a hybrid where you defined your models in your own code and then built the static site generator with those models—​so that a Rust compiler is needed to build the generator in the first place and when you make any data model changes, but not at other times.

Ideally this would also facilitate handling computed values much more cleanly than repetition of code and the occasional Tera macro. I often have two versions of a field, sometimes even three, for use in different contexts: the main value is full HTML (since I like to be able to use formatting in my titles and such), but in plain‐text contexts you may want or need a value different to the tag‐stripped HTML.Especially since Tera’s striptags filter doesn’t decode HTML entities. And I’d like cleaner validation routines on field values than what I currently implement in Tera templates with {% if %} and {{ throw() }} (which admittedly works pretty well).

Probably my favourite thing about Zola is that it’s all a single self‐contained binary. My Hyde installation was never trivial to get going, and it would now be decidedly difficult to get a new installation goingFor example: the Arch User Repository package that I have installed on my server to build it is no longer installable due to depending on two old packages that have been removed from the community repository but which haven’t reappeared in AUR., because it depends on quite a bit of obsolete software. Lektor was better, but I still made life harder for myself by insisting on patching it to work on Python 3, back before it did so officially, and by adding various plugins. Some plugins like lektor-tags also had some niggles and a tendency to cause its development server to fall over until you ran lektor clean. But don’t take this as guidance for now; we’re talking about the state of things 2–3 years ago. I had to fork a few packages to get them to do just what I wanted.

But all of those troubles are brushed away with Zola, which is a simple single binary that you can install without any difficulty.

At this time I have forked Zola to change how a few things work, and most notably to generate Atom feeds instead of RSS. I intend to try to upstream these and any further changes that I make.

Syntax highlighting, and Markdown complaints

On the web, most people only syntax highlight blocks of code, not inline code; but I’ve always liked to do things thoroughly.

In Hyde, I made it so I could write {{ 'return &42'|rust }} in my articles and it’d feed it through Pygments, emitting something like return &42; and there was {% syntax rust %}…{% endsyntax %} for code blocks. (I deliberately didn’t opt in to using Markdown or reStructuredText; it was all just HTML + Jinja2.)

I also wrapped the built‐in syntax highlighting function with one that turned « and » into <mark> and </mark>, by careful string replacements on Pygments’ output.

In Lektor, I was experimenting with using Lektor’s flow blocks for structure, and so implemented a code flow block; this didn’t fully satisfy me because it didn’t handle inline code highlighting, and I also never quite liked having to use the web editor or being careful with manual edits of the contents.lr files. So I then implemented an html_jinja field type that would feed content through Jinja2, so that I could return to doing things as I had in Hyde. This satisfied me pretty well.

Since those old days, I’ve thrown out the Vim colour scheme I used to use (molokai), and written my own, much simpler one; I started with just black on white with keywords bold, and then added things slowly over time, until it matched what you see on this website fairly closely: black on white, keywords bold, strings red, comments green italics, numbers blue, and the odd extra per‐language tweak here and there.

Well, I wanted my inline syntax highlighting here too, and Markdown is actively hostile to these sorts of things. I thought about messing around with Zola internals to do what I wanted, but I ended up deciding to leave it alone, and just do all syntax highlighting manually, with the sort of markup I want. So I actually have Zola’s syntax highlighting completely disabled. I’ll probably write myself a little program to do a good first pass of my style of highlighting, at some point. Maybe make a set of Vimscript macros and commands. Not sure yet.

I dislike Markdown being forced on me, with no proper raw HTML mode like reStructuredText has, given how it treats blank lines followed by a non‐tag as liberty to start a new paragraph or a code block if it’s indented.I’d love to disable indented code block support in Markdown; that’d solve half my troubles. (The trouble is that Markdown doesn’t know or care that you’ve opened a <pre> tag.) That is my greatest objection to Zola’s mode of operation by far. So far, I’ve just put ZWSP (Compose z Space) on all significant blank lines, because that satisfies both Zola and pulldown-cmark, and then removed them again in the template with the replace filterIncidentally, Tera doesn’t do escapes in its string literals, which feels very strange; but it doesn’t mind literal line breaks and U+200B in the middle of the string, so I can live with it until I need all three styles of quotes in one Tera string (", ', `). Like here. Then HTML entity encoding has to come into play. 😝, so that code isn’t broken if you copy-and-paste it.

I would not call Zola mature. It has lots of bugs, niggles and inconsistencies. Its template language Tera is regularly confusing, several times using the same syntax for two or three wildly different things, and I regularly had to replace a chaining one‐liner with a three‐ or five‐liner to do the same work in multiple steps, because its syntax basically doesn’t compose at all.

I think that Zola’s pure‐Markdown pipeline is misguided, particularly as regards its interactions with shortcodes. When you have to replace \n\s*\n with \n to prevent Markdown breaking things, you’ve gone wrong somewhere.It occurs to me, immediately before publishing this, that something like my ZWSP replacement technique would work neatly here.

But with all of these complaints, I still have something that works, and which I can work with. Zola is by no means bad. These matters are just why I describe this site as “differently hacked together” rather than “not hacked together” as I had initially hoped. I put unusual stresses on these sorts of systems, using them in ways that normal people don’t, so I expect to run into problems like these in the darker, less‐well‐explored corners. It’s self‐inflicted, really. Little of my criticism applies for users that just want to write simple content and aren’t too fussed about the presentation.

I also pine for reStructuredText’s structured extensibility. In some places Zola’s shortcodes can work (e.g. my sidenotes{{ sidenote(src="…", note="…") }} aren’t awful, and are definitely prettier than writing the HTML out manually, though they’re not quite as pretty in the source as they are in the Sphinx/reStructuredText project I first made them for); but even in those places, Zola + Markdown amounts to C‐style text‐based macros, while reStructuredText is Rust’s AST‐based hygenic macros. Shortcodes straddle an uncomfortable space where “is it Markdown or not? Will Markdown inline formatting occur?” uncertainty applies all the time.

As mentioned, I’m doing my syntax highlighting manually at present, block and inline; even if I go back to automatic (which would be sane, by the way) and implement a Tera function in Rust so I can make a shortcode to do inline syntax highlighting, it won’t work inside sidenotes, because these structures don’t nest. That’d then leave me with crazy options like annotating backtick code blocks with the language to highlight it with (y’know, something like :rust:`fn foo() {}`That’s the exact syntax I’ve used in reStructuredText before, in case you’re wondering.), and plugging into the Markdown parser in some way.

I really don’t like Markdown much. I use it extensively these days, but its HTML basis is just such a bad idea, and it lacks sane extension points. I’m really sad that it has won this space, because it’s just so technically unsound, and irredeemably so.

The future

When I moved to Lektor, I deliberately intended that it would be temporary, probably only lasting a year or three. Well, that has happened, though I didn’t ever actually publish the Lektor‐powered site. Zola and the latest site design have answered most of the reason why I intended the Lektor site to be temporary. So I’m pretty happy with this for now.

There are still a few things that feel a little off, compared with my ideal system; and I have a few features in mind that would benefit from not being a fully‐static site, such as smarter 404 pages, and filtering by multiple tags (a+b for posts tagged both A and B, even a-b for posts tagged A but not B) without exponential growth in files. I like Lektor’s data model ideas, too, though the likes of Netlify CMS probably make that not so valuable any more.

Eventually I may make a new type of website. But for now, this one’ll do nicely.