Making this website

Some technical talk, some thoughts on the modern web, some thoughts on the old web, and some thoughts on picking the good parts from both.

Posted 20th March 2023.

This is not the first post on this URL, but it is the first one since it became a proper website. Until now, anything I’ve put here has just been it’s own little page so I had a link to send to people. (Specifically, there were two posts, one of which I don’t like anymore so I’m going to pretend it never existed.)

I’ve spent quite a lot of hours building the site you’re reading this on, so here is my recollection of those hours.

As will probably be the case with most posts on this website, I’ll be talking about the specific things that were interesting to me. Here, that’s the things where I’ve learned something new in the process of doing them. I won’t explain everything, but I did try to write the code in a way where someone with some prior experience would be able to figure out how a specific thing is done with the help of their browsers development tools and a bit of investigating. So feel free to right click something and choose “Inspect.”

The Web has changed, for the [better/worse]

When you click a link to a website you haven’t seen before, what is more disappointing: A “bad” design full of clashing colors and unintuitive layouts, or the same bland, corporate design that you’ve seen a million times before? If you prefer the bland one, you’re in luck. The internet has gone very stale over time.

Personally, on the rare occasion that I end up on the first type of website, I actually feel much more at home than I would on one of those “professional” websites competing for their spot on the Google search results, making me click away an average of two and a half popups before I get to see the actual page and then collecting any data they can on me in the background to sell to advertisers.

What do we do about this? Trying to convince corporations to let their web designers create more fun things that aren’t actively hostile to the user might be a lost cause. The only thing they care about is money, after all. An exploited user tends to bring in more money than a happy one. And creativity is risky if your goal is profit.

But if we can get more individuals to make their own personal websites again, like they did in ye olden days, the future looks a lot brighter. Maybe then we can all become better at this craft and rival the corporations, while bringing our creativity with us.

Okay, so the modern web sucks in terms of what it’s used for. But that doesn’t mean everything has gotten worse.

To illustrate: When I was eleven, I made a forum (which is lost to time now). I remember that the standard way to implement round borders back then was to use images. That is, create four images, one for each border corner, in your favourite graphics software, upload them, and create an HTML table structure that places those images at the corners of the box you want to give a rounded border to. Yes, that meant that changing the box background colour, or the colour of the element behind it, would require making new images to reflect that.

Now, CSS border-radius is supported by every major browser.

The standards behind the web have evolved to not only make some things easier, but also enable entirely new things that would not have been possible in the Geocities era. And that’s good for creativity.

Okay, so… What am I doing here? A few things. I need a place to write at length about my current obsessions. I like what Neocities is doing. And I feel expressing myself would always be limited on any social media platform. I enjoy spending time on Tumblr, but it’s ultimately still a company-owned social network that just happens to be a little bit more customizable and a little bit more reluctant towards selling out its users. (Neocities technically calls themselves a social network too, but that’s not the main focus, and I could turn all of that off if I wanted to.)

I’m not trying to recreate the old web here (though I’m taking a little inspiration with the pixelated style), more so, I’m trying to recreate the spirit of that era. All that’s needed to achieve that is just having a creative vision, and most importantly, having fun.

Jekyll

In the spirit of keeping it simple, stupid, I tried to keep as close to the underlying web standards (HTML, CSS, JavaScript) as possible, without introducing any fancy tools inbetween. Because of this I initially considered just writing all the files manually in the Neocities editor.

But this wouldn’t be very maintanable in the long run. For example, if I wanted to change the footer that’s consistent across all pages, I’d have to copy-paste it into every HTML file individually. Similarly, I’d have to manually update the list of articles on the home page any time I write one. Both of these are annoying and ripe for accidentally creating inconsistencies. So I needed some kind of content management tool to save future me (and current me) some headaches.

Specifically, I wanted:

  • A way to only declare repeating sections once and automatically build them into all pages
  • A way to convert markdown to HTML for blog posts so they’re easier to write
  • A way to keep an up-to-date list of posts on the home page

One of the tools that Neocities themselves recommend is Jekyll. It’s open source and seems to be designed specifically with blogs in mind, so it sounded good to me. It turns out, it’s very lightweight and pretty easy to learn with the Step by Step Tutorial.

Using Jekyll, my local file system looks like something like this:

space-cats-space
|- _layouts
| |- default.html
| |- post.html
|- _posts
| |- 2023-03-20-making-this-website.md
| |- 2022-12-07-explosher-kits.md
| |- ...
|- _site
| |- ...
|- assets
| |- css
| | |- ...
| |- fonts
| | |- ...
| |- images
| | |- ...
| |- js
| | |- ...
|- _config.yml
|- index.html
|- posts.html
|- ...

After running jekyll build, all the generated static files will be in the _site directory and I can then upload them to Neocities through their CLI, which just so happens to also run on Ruby.

Blog posts go in the _posts directory and I can write them like this:

---
layout: post
category: "Math & Programming"
title: "Making this website"
description: "Some technical talk, some thoughts on the modern web, some thoughts on the old web, and some thoughts on picking the good parts from both."
---

This is not the first post on this URL, but ...

And then I can list them all by category on the home page like this:

{% for category in site.categories reversed %}
  {% capture category_name %}{{ category | first }}{% endcapture %}
  <li>
    <h3 id="{{ category_name | slugify }}">{{ category_name }}</h3>
    <ul class="post-list">
      {% for post in site.categories[category_name] limit:3 %}
        <li>
          <a href="{{ post.url }}">{{ post.title }}</a>
          <p class="description">{{ post.date | date_to_string }} - {{ post.description }}</p>
        </li>
      {% endfor %}
    </ul>
  </li>
{% endfor %}

The code above is as complex as it gets. Most of the files are very clean HTML or Markdown with minimal {{jekyll}} stuff in them.

Jekyll also comes with some extra perks, like using jekyll serve --livereload to develop the site locally and have an automatically updating preview in my browser on the side. At the same time, it keeps everything as simple as possible while giving me the features I wanted. Awesome!

JavaScript animations

There are so many different ways to do the same thing in JavaScript. I’ve rewritten the code used for the site several times because I found out there’s a cleaner way to do the thing I was doing. None of that has any actual impact on performance or anything, it just makes the code more satisfying to look at, and makes me feel like I’ve learned something.

For example, take the little intro animation you see on the homepage (check it out if you haven’t). To implement the letter-by-letter typing I had to do timed events in JavaScript, which are a bit of a pain. Especially because it’s actually two consecutive typing animations at different speeds, one for the text, and one for the ASCII art. This was the progression of my approaches:

  1. Two loops in a single async function, with a “sleep” function I copied from StackOverflow in each iteration (I didn’t really understand async/await yet. Also this one actually had an issue of not allowing for intervals smaller than a few milliseconds on most browsers).
  2. A general typeText() function that calculates the point in time each letter should be added and sets a bunch of setTimeouts all at once to the right intervals. The second call to it just added extra time to the timeouts so that it happened after the first.
  3. The same function but it now returns a Promise, resolving with the final setTimeout, so that in the main function I can promise chain with .then().
  4. The same but now again as an async function (this time I actually understood what that meant). Still does the same thing but removes the return new Promise(...) from the function.
  5. Revert to version 3 because in the async function setTimeout(resolve, timeBetween * i) became await new Promise(() => setTimeout(resolve, timeBetween * i)) which actually made it less readable overall.

I want to reiterate: Versions 2 through 5 function the same, it’s just the code that’s different.

There are more ways to do this, for example instead of setTimeout you could use setInterval with a condition check at the end of it that clears the interval if the counter variable is high enough. Though that would have the same issue as the method with the sleep function in this case…

JavaScript is generally a somewhat confusing language (to me, at least). I had to learn about exactly how it handles the this keyword when I was working with classes to make the “Release the cats” button, and it was baffling, to say the least. I don’t even want to try to explain it here. That button was another place where I rewrote the code quite a lot in an attempt to organize it better.

Point is, I spent a lot of time doing optimisations where they’re not needed, but it gave me a better understanding of the language, so it was still time well spent, I think.

Responsiveness is [easy/hard]

Responsive design is designing and coding your website in a way that works reasonably well on any device, regardless of screen size. This can be easy, or it can be not so easy. I tried to keep responsiveness in mind from the beginning and make a layout that is interesting but flexible, and that won’t have to be retrofitted to other screen sizes later.

Most of this happens in CSS, though it’s a consideration everywhere (the background shader, which we’ll talk about later, is also made with different resolutions in mind, and stuff like the “Release the cats” function has to account for it too). When it did come to CSS, I mostly avoided breakpoints using media queries and tried to stick to more gradual methods. Which meant I ended up with some very funky configurations like this:

h1 {
    font-size: clamp(2rem, 5vw, 2.8rem);
}

Here the level one headings scale with the viewport width, but are never smaller than 2rem, or larger than 2.8rem. Picking the right numbers was a matter of testing different values by resizing my browser window. To be honest, in this case just setting the heading to a single constant size would have probably been fine, but unfortunately I’m a perfectionist.

By the way, did you know that the vw unit in CSS corresponds to 1% of the user’s viewport width? Except it actually doesn’t, because vw includes scrollbars, and if you set an element to be 100vw wide on a page you can scroll down on, it will overflow to the right of what’s actually visible. Sigh…

As you can see, this can get a bit complex. There can be a tradeoff with creativity sometimes, but I guess I’ll just have to be more creative to balance that out… Also, I think being able to create something that can be experienced from all over the world in as many different ways as possible is what makes the web so cool.

On that note, accessibility. It’s a thing!

A (properly marked up) HTML page with no CSS styling or JavaScript is, more or less, accessible by default. Issues start to arise when you add complex styling or features. Like an intro animation that hides the main page content with an overlay and types out a sentence letter by letter. Oh, whoops.

I first considered just making the intro aria-hidden and letting screen readers access the page content behind it early, since I don’t believe they’d know how to deal with the constantly updating text and the intro isn’t essential information. But then that would create a mismatch between visuals and the accessibility tree, which can be confusing to sighted screen reader users (and let sighted keyboard users tab navigate to things they shouldn’t be able to yet)…

As it stands now, there’s a visually hidden text that has the full message (without the ASCII art) immediately, while the dynamically changing part is aria-hidden. It’s not too confusing, I hope?

I actually installed a screen reader and spent a day trying to use it, which was a fun experiment. (I hope any problems I had navigating this site were due to my inexperience with screen readers and not the accessibility of the site.)

Small issues aside, the page should work well enough in any reasonable, modern context. What about older browsers? Well, uh, no guarantees there, sorry…

WebGL

Let’s get fancy. I’ve been learning shader programming recently, so I really wanted to make use of it while making this site. In theory, WebGL should allow to easily embed a GLSL fragment shader into the website to use as a background. In practice, it turned out to be a bit more complicated than I thought it would be.

The amount of lines you need to get a basic “Hello World” output (which for fragment shaders is a gradient) is mind-boggling. I’d link a good tutorial, but personally I ended up jumping between a bunch of different ones until I eventually had at least a vague understanding of what they’re all trying to tell me.

You can find the code I ultimately ended up with at /assets/js/webgl-draw.js. It sets up WebGL to draw on the canvas with ID #gl-canvas (yes, this means it only works for one canvas per page, but that’s fine in this case) taking the shaders from inline-scripts #vertex-shader and #fragment-shader. We don’t really care about the vertex shader here, but we still need it to display a quad mesh that we can then draw the fragment shader on. It should look like this:

attribute vec2 a_position;
void main() {
    gl_Position = vec4(a_position, 0.0, 1.0);
}

Which just takes the coordinates we give it in the JavaScript code and passes them on.

webgl-draw.js also determines the size of the canvas and passes it to the shaders as a uniform. While we’re at it, we also pass a variable keeping track of the time so we can animate.

Once everything is set up, we can have some fun with GLSL in the fragment shader and make a starry background! Here’s a Shadertoy version of that shader, for your convenience. It combines a few basic shader concepts, such as SDFs, tiling, and noise. The specific star shape I used actually has a very simple signed distance function that I figured out by filling a page with doodles and equations that would be incomprehensible to anyone else. That’s math!

Halfway through coding I realized that, oh, this is going to be rendering in real time on mobile devices, maybe I shouldn’t be calculating noise functions every frame. So then I had to figure out how to pass a texture to the shader in WebGL, for which I didn’t bother trying to understand exactly what’s going on and just copied an entire function from MDN. Once we have that texture, we can do fBM on it and we get some pretty neat clouds.

What’s nice about WebGL is that we can get a data URL of the rendered shader with canvas.toDataURL("image/png"). This was very convenient to get the fallback background images that display if animations are disabled or if WebGL isn’t supported.

What’s next?

I now have a fun little website to post long texts about whatever I happen to be obsessed with at the moment. There are still some things I might add, such as:

  • An art gallery showing off some art I did over the years.
  • Syntax hightlighting on code blocks, and a way to display math equations in posts for when I inevitably talk about math.
  • Comments on posts. I’m unsure about this, for one because I’d have to rely on an external service to achieve that on Neocities, and also because I kind of like the idea of a fully static website that just exists as it is. But… maybe.
  • Whatever random other things I can think of.

Until I find motivation to do that, I’ll go back to… whatever else I feel like doing. I took my old MIDI keyboard back out of the closet today, so that might be a sign of what my brain will focus on next. Maybe I’ll write something about it.