Let's Hold Hands

SourceI'm not completely sure where I'm going with this, but stay with me, ok?
Let's hold hands. No? OK, fine, that's fine too.

No it's NOT fine!

Let's Hold Hands is a tiny web app where you can:

  1. hold hands.

You can use it in two ways:

The entire thing takes 34-45 seconds. Give it a go and let me know what you think!

Why I made it

Short version: it's a fun communication problem!

I also...

I also really need to learn how to draw hands that don't look like they previously belonged to a Lich (dare I say, second-hand?)

Slightly longer version:

I enjoy working on and learning about toys/games that facilitate:

An example of a game that achieves this so well is Journey, where players can communicate through a series chirps and movements, only being able to learn each other's names at the end of the game (also, a completely optional step).

One of my projects dealing with a similar subject matter is Space Kalimba/Nothing, Together from 2023. That tool itself is pretty trivial: you join a room as a little nameless star. You can click on another star on the screen and send it a short melody. That's it.

One of the things I've learned when I was messing with Nothing, Together was this: the non-verbal interactions we have with people through a medium like this can feel deeper and more important than communicating through text. How did I learn about this? People would bring it up (Say Hi).

Why does it work? I'm still fermenting this in my head, but here's the gist: when we don't have words, the only thing left is touch. And playing with Nothing, Together feels a bit like touch.

How I made it

Over 2-3 weeks, in short bursts, instead of writing here. I also spent a weekend on getting the matching algorithm right.

Process

The process was as follows:

Here are some example Procreate sketches I used for the UI:

Related: Share your unfinished, scrappy work, 2-2-2 Project Scoping Technique

Stack

It's pretty rudimentary, so we'll keep it short:

  1. Preact w. signals, CSS modules, Typescript for the front-end
  2. PartyKit for the backend
  3. Vercel to deploy the front-end

Why these tools specifically? 1. and 3. because I don't have to think about those tools when I'm using them. They're somewhat boring and out of my way.

PartyKit is probably the most interesting part of the stack. In short, PartyKit is a WebSocket/HTTP wrapper around Durable Objects . You define your server-side code as a class with methods that respond to WebSocket messages or HTTP requests. The data associated with the class can be easily persisted.

I've been building realtime apps since 2011 and now in 2025, PartyKit would be my default choice for a starter project because it's opinionated in a way that suits me. It solves many of the annoyances that normally would detract me from even picking up a project like this. I wish we had PartyKit before we came up with tools like AWS Lambda.

Matching and managing rooms

We have two PartyKit Parties:

  1. Rooms → assigning and managing rooms
  2. Counter → receives and broadcasts messages within each hand-holding session

For me the tricky or somewhat unintuitive part here was mapping the PartyKit language into something I'm familiar with, i.e. it feels awkward to have a PartyKit party with only one room, responsible for generating room IDs to be used by other PartyKit rooms (PartyKit Rooms and Parties).

Sketchy rendering effects

Good design feels consistent. The sketches of hands or the hold button are drawn by hand, but I was curious if I could use SVG filters to make the more repetitive parts of the UI, such as buttons.

I think it looks fine. The main issue there was performance, esp. with more instances of SVG-generated backgrounds. Surprisingly, the culprit was not memory but computation related. I'll post another write-up on that later (How to create sketchy visuals in SVG).

Procedural animation

Hands are animated and laid out procedurally. The surprisingly tricky part here was the layout. Hands need to be distributed evenly but their distance needs to work for most aspect ratios so their lengths needed to be calculated. I noticed that I got a bit lost in that, so I created a separate project where I isolated the problem and tackled it separately.

Getting the tone right

Another challenge was getting the tone right. I was working on this around the time of Valentines, and this is not a Valentine's Day card. Solution: wait for a week.

How did it go

At the time of writing this I've shared it only with a small group of people:

I'm content, what more could I have asked for?

Next


The magic hand I drew instead of hanging the laundry, trying to convince myself to hang laundry, an ancient art known as procrastinomancy.

First, I'll need to get back to more frequent updates. I got sucked into a few problems I didn't enjoy or have to solve. The more I worked on this, the more I kept raising the bar. I know that what I built is too complex for what it is. The action here is to share a few, smaller technical notes about what I've learned in the past few weeks. That was the next non-technical step. Now, let's talk myriapod:

Add centipedes

Broadcast empty rooms so that people who opened the app during low traffic can find a pair

If a person is waiting for a partner for longer than 1 minute, a Bluesky bot will fire a message with the link to the room.

What I would love to see/facilitate are more anonymous, serendipitous interactions between people. That's why to me the 2-person mode where we are paired with strangers feels more interesting and more rewarding.

This works perfectly if the site goes viral and new rooms are created several times per minute, but most usage patterns, with those < 1 minute long sessions, most people are likely to miss each other.

Virality is not the goal here, so adding a little channel with broadcasting empty rooms seems like a good compromise between scale and practicality.

For instance: I imagine an interaction where someone happens to open Let's Hold Hands when they're feeling down (or just bored), then another person pops up on the screen, their hand reaches out, you hang out for 30s and leave. You leave only knowing that you spent that time with another, nameless human being. There's no expectation to talk, to learn about someone, or even to be for someone. Two people in a dark room saying to each other I am I am I am.

That's all for today, thanks for reading!

Want to receive my work as I publish it? Subscribe via email or RSS.

a giant foot-shaped snail with a house on its back. the house is still in construction, with a big crane towering above it The image is a stylized black-and-white illustration. In the lower left corner, there is a small, cozy-looking house with smoke rising from its chimney. The smoke, however, does not dissipate into the air but instead forms a dark, looming cloud. Within the cloud, the silhouette of a large, menacing face is visible, with its eyes and nose peeking through the darkness. The creature, perhaps a cat, appears to be watching over the house ominously, creating a sense of foreboding or unease.