Projects

This portfolio's main menu, over a field of flowers at night
Slide 1 of 1 for This Portfolio Website

This Portfolio Website

The site you're on. A portfolio built like a game menu, with keyboard controls run by state machines, and a map, dialogue and achievements to find.

Tech stack

Frontend

  • Next.js
  • React
  • Tailwind CSS
  • MDX

Controls

  • XState

Motion & 3D

  • Motion
  • Three.js
  • React Three Fiber

Realtime

  • Yjs
  • Cloudflare Workers

Testing

  • Vitest
  • Playwright

Hosting

  • Vercel

Writeup

Features

There's a lot more going on here than it looks like, so here's everything the site can do.

Controls

  • The whole site works from the keyboard like a game menu, with the arrow keys moving a cursor between things and Space or Enter to confirm.
  • Every control can be rebound in System, or swapped to a preset.
  • Long pages like About are read with the arrow keys too, stopping at each heading, link and image along the way.
  • State machines run the navigation, so every key press has one clear result.
  • On phones it turns into a compact layout made for tapping.

First visit

  • A setup screen walks you through controls, sound, theme and multiplayer before you start, or you can skip it and take a quick look.
  • If you arrive from a link straight to a page, it lets you read first and only asks once if you want the full experience.

Story and dialogue

  • Two voices talk to you as you explore: ANTON (me) and SYSTEM (the site itself).
  • Dialogue types itself out, can be fast forwarded, and is picked by rules depending on what you've done so far.
  • My About bio gets edited live by my friends, and you can hover over each edit to see the original.
  • Every character has a page in Extras with all the lines you've heard from them.

The map

  • Press M to open a map of the site, drawn like a city with a room for every page.
  • Rooms start under fog of war and clear as you visit them.
  • You can warp to any room you've already found, with a little walk and a screen flash on the way.
  • The city changes every day, and everyone online sees the same one.

Achievements and saving

  • Your progress is saved in your browser: the rooms you've found, the lines you've heard and what you've unlocked.
  • There are achievements to find, including hidden and secret ones.
  • New game in System wipes it all if you want to start again.

Multiplayer

  • Turn on collaboration to see other visitors on the site live, with their cursors coloured per person.
  • You can chat with them, and see where they are on the map.
  • It runs on Cloudflare Durable Objects as the lobby, with Yjs passing the updates around.

Visuals

  • The title screen is a 3D field of roses at night, with depth of field and bloom, and focus that follows your cursor.
  • Leaving the title screen shatters it into the next page.
  • Six themes to pick from in System.
  • Dust drifts behind the home screen and reacts to your cursor.

Sound

  • Menu sounds are generated in the browser instead of loaded from files. It's off by default.
  • Search the whole site, and the page you land on highlights what you searched for.

The 404 page

  • A lost page gets its own scene: a lone rose in the dark, and you leave by walking into the light.
  • It guesses which page you meant and offers it.
  • Sharing a link shows a preview card, photographed from the real site rather than drawn separately.

Debug mode

  • A hidden overlay shows how the navigation works underneath: the regions, the targets and what each key press did.

The dialogue engine and fact store

The map, the achievements and what ANTON and SYSTEM say all need to remember things about you. Which rooms have you been in? Have you found the hidden room? Is this the first time you've warped to someone? Instead of each of those keeping its own flags, they all share one set of facts, one engine that reacts to them, and one save file.

I took most of the ideas from games. The engine is based on Elan Ruskin's GDC talk on how Valve picks dialogue in Left 4 Dead, which games like Firewatch use too. The achievements copy how Steam does it.

Facts

A fact is just a named number. Whether you've seen a room is a 1 or a 0, how many times you've warped to someone is a count, and so on. Everything is a number on purpose: if saves ever sync to a server, two copies can be merged by taking the bigger value of each fact, with no special cases.

Facts have a scope. Most are saved, some are kept per room, and some only last for the session, like who just warped to you, so short-lived things don't clutter the save.

Events and rules

The rest of the site never changes facts or unlocks things itself. It just reports what happened, like "this room was discovered" or "they warped to another visitor".

A rule listens for one of those events and has conditions on the facts, like "has warped to someone at least once". When an event comes in, the engine gathers every rule listening for it, sorts them so the most specific ones come first, and runs the first one whose conditions pass. So a rule that checks three things beats a general one that checks one, which is how a general line can have special versions without any if statements.

A rule can say a line, start a conversation, unlock an achievement, or change a fact. Events and rules are facts too, counting how many times they've happened, so a line that should only play once doesn't need its own "seen" flag.

Achievements

An achievement is just a rule whose action is to unlock. Cartographer listens for rooms being discovered and checks whether every room has been found. Once something unlocks it's written down with the time and never taken back, so adding a new page later doesn't take Cartographer off anyone who already has it.

Conversations

The conversations themselves run on a state machine. Each one is a plain script of scenes, lines and choices that sits next to the page it belongs to, like the 404 or the setup screen. The lines type themselves out, you can fast forward, and choices can be hidden or shown depending on flags. A choice can also save a fact, and when it does it goes through the same engine as everything else, so the engine is the only thing that ever changes the save.

Turning off "Story and notifications" in System quietens both voices, but the rules keep running and the save keeps filling, so nothing is lost if you turn it back on.