Colophon
About this site
Why this page exists
This site is the one project here I built alone, so it is also the only one where every decision is mine to defend. What follows is not a feature list. It is the handful of choices that were not obvious, and what each one cost.
Three languages, one source of truth
The obvious way to translate a site is one dictionary holding every language, English included. I kept English in the HTML instead, and cache it from the page when it loads.
That buys two things: the site still reads correctly with JavaScript disabled, and the Dutch and French dictionaries are a third smaller than they would otherwise be. The cost is real. English cannot be lazy-loaded the way the other two are, and it has to be captured before the first switch or it is gone.
A theme toggle that flips rather than fades
Elements here already carry transitions for their hover states, so switching themes cross-fades every colour on the page for free. In theory that is smooth. In practice the Download CV button came out unreadable in light mode, and measuring the computed contrast showed why: the text was still interpolating toward its new colour while the background behind it had already arrived.
So the switch suppresses transitions for a single frame and the theme flips instantly. It is the correct behaviour, and it is also what better-built sites do. I would not have found it by looking at a screenshot.
Flags that render on Windows
The language picker draws its flags as inline SVG rather than emoji. An emoji flag is a pair of regional indicator letters, and Windows does not render that pair as a flag at all, it shows the letters. I build on Windows, so the emoji version would have been broken on my own machine before anyone else ever saw it.
Motion that fails safe
Scroll reveals work by hiding content until it enters the viewport, which means a JavaScript failure can leave a page blank below the fold. Here the hidden state is defined only under html.js, a class the script itself adds, and only inside prefers-reduced-motion: no-preference.
If the script never runs, nothing was ever hidden. If a visitor has asked for reduced motion, the rule is not even parsed. Both are cheap to arrange up front and impossible to retrofit convincingly.
Contrast measured, not eyeballed
The small monospace lines under each project looked fine to me. Measured, they came out at roughly 4.4:1, just under the 4.5:1 that WCAG AA asks for at that size. The cause was stacking an opacity on a colour that was already muted.
The fix is invisible. That is exactly why it needed measuring rather than judging.
One build, any host
Vite is configured with a relative base, so the same build works at a domain root and under a sub-path such as a GitHub Pages project URL. I checked it by serving the production build from a sub-directory rather than assuming it would hold, which is how I caught a link that was still absolute and would have broken there.
Labels without numbers
The section labels are plain: Selected work, Experience, About. Not 01, 02, 03. Numbering implies a sequence the reader is meant to follow, and a list of projects is a set, not a sequence. An earlier version of this site numbered them, which is part of why it read like a template.
Set in
Manrope and IBM Plex Mono. Built with Vite, TypeScript and SCSS, and no framework. The homepage opens directly to the headline and rotating wireframe globe, drawn on a canvas. Letter masks and pointer interactions use native browser APIs.