i  <esau

my 10 “Computers” posts

the foothills of automated customer support

I been noticing more smarter AI in customer service these days.

A recent example: I docked my BIXI but the thing glitched and the ride didn't terminate. I only realized when I tried to get my next bike out.

In the past when this happened to me, I just called up BIXI support, went through a short phone menu, and explained the situation to someone on the phone. They verified my identity and manually terminated the ride so I could go on my way.

Basically, the ideal phone-based customer support experience. The wait wasn't long, the procedure was efficient, each step made sense, and it solved my problem.

This time, the app prompted me to ask for support via "chat" first. Historically, this could have meant all sorts of things: a naive keyword recognition program that would recommend FAQ entries to me; a tech support worker from a country with a low median wage pasting in responses from a script, 5 tickets at a time; occasionally, the type of person I'd been talking to on the phone, but over text.

This time, it was some sort of LLM. It dynamically and patiently walked me through the identification and triage process. It handled my typos and information I sent bit-by-bit.

Eventually, it concluded the issue needed human involvement. Presumably at the end of its instructions it has a "If asking the customer to jiggle the bike doesn't fix the problem, request that they phone a human." part of its prompt.

This was fine. The trouble was that I then had to do all the same stuff with the human. The same identification process, the same identification of the nearest street intersection, the same bike jiggling. Finally, the human terminated my ride.

It would clearly be better if I could call the human and have them say "I see you've already gone through all the steps with our AI. I'm going to unlock the bike now."

Better yet (though not something BIXI alone can implement), identifying myself is done via a two-factor prompt on my phone (get on this, governments!) and I can use text-to-speech to compose my messages to the AI. Maybe we'll even eventually find them reliable enough to let LLMs terminate rides themselves!

As it stands, the experience feels like costs are being cut. It doesn't need to. Automated customer support could be faster, cheaper, and more convenient than the best phone support I've had.

A call support worker
Was that as good for you as it was for me?

I'm still skeptical of this future. The companies that have already automated as much of their customer support as humanly possible are miserable to deal with, even though in a sense I'm grateful for the downward pressure they put on prices. These are typically companies of enormous scale. The BIXIs of the world might be able to take a different path.

once more, worse is better

A lot of people on Mastodon are there because they believe decentralized and open-source techologies are essential tools for resisting coercive power. The fediverse is innately resilient to censorship, advertising, and hostile takeovers.

For these reasons, I made the switch from twitter sometime in the early 2020s, but I think it's important to remind ourselves that these virtuous properties are not a panacea to all the ills of social media.

It doesn't matter whose database the posts exist in: we can all still be ragebaited. It's a fundamental vulnerability of our psychology, with or without Elon Musk intentionally exploiting it.

A stick figure sitting at a computer. A voice from another room is asking them to come to bed. The stick figure replies 'I can't. Someone is wrong on the internet.'
this comic is from 2008

I've maintained for a long time that the good part of Mastodon is that it's kind of shit by default. You have to put quite a bit of effort into finding people to follow and tweaking your settings to get it to surface a lot of interesting content. I haven't done this. I follow a few randos and some bots that post clipart.

I like the fact that when I check the fridge1, there's just the same head of cabbage that was there last I checked. Paradoxically, the better Mastodon gets, the more I'll check it, the less good for me it will be.

Recently, someone I follow started boosting posts I found to be confidently wrong. I started getting emotionally invested in the thoughts of complete strangers. Getting provoked. I wanted to write replies to correct their misconceptions.

I've mostly been able to resist: it would not change anyone's mind. It would take a lot of time. I would get even more emotionally preoccupied by a hamster wheel.

Instead, I think I'll employ another cool thing about open-source technology: you can usually adapt it in ways to suit your needs2. I can filter this person's boosts and hopefully that will be sufficient.

But there's a tension here. For the structural factors outlined in the introduction, I want the fediverse to grow. For it to grow, it needs to attract users that aren't already sold on the structural selling points - it needs to be better. For it to be better, if we optimize for engagement, it could easily become worse.

A ragebaiting machine that people can tinker with is still better than one they're forced to use, but I will maintain my wary distance in either case.

Keep reading →

defeating git rigour fatigue with jujutsu

This post assumes a basic level of familiarity with the jujutsu version control system. If you haven't used jujutsu, you'll still get the gist of the idea, but I recommend reading Steve's Jujutsu tutorial after.

When developing a large feature, writing Good Commits is hard.

And by Good Commits, I mean something like:

define types add DB functions server CRUD client API client UI

This allows reviewers to step through your pull request in small bites, with each set of changes scoped to a single aspect of the feature.

So, naturally, here's what I do instead:

define types add DB functions WIP test code server CRUD client API and UI fix DB function fix UI bug refactor CRUD fix another UI bug

Latter commits overwrite work that was done in earlier commits and the story breaks.⚖️

Jujutsu makes it easier to hop around commits and iterate quickly on compartmentalized changesets, but it's still effortful and I get averse.🤖

jj absorb helps somewhat, as does jj squash -i, but they both have their downsides:

  1. absorb assigns the changes based on whichever previous commit most recently touched those files, which sometimes doesn't actually correspond to which commit should own these particular changes.
  2. squash can get you stuck in merge conflict hell if your boundaries aren't extremely clean.

So here's a solution to this problem of "git rigour fatigue" that I've come up with.

For this example, let's represent commits visually. Imagine red represents changes to the type definitions, blue to the UI and so on:

Mayhem. Our first commit is a mix of red and blue. We touch red in multiple places!

To fix this, let's create our ideal commit history first, using jj new -B messy-first -m 'red'

Then we can do the rest. (I switch to jj new -A red -m 'blue' at this point)

Then we squash all the commits with actual changes in them into one with jj squash --from messy-first..messy-last --into messy-first

Then we use jj squash -i --from messy-first --into red and pick out the red changes, putting them into the red box:

And so on:

Eventually everything's in the right place and the "everything commit" is empty.

For large features, I find this workflow far easier than having to maintain strict git rigour for the lifecycle of the feature's development. It's easier to make improvised commits with temp debugging state in them and tidy it all up in one sweep at the end.

preemptions:

  1. I don't have a good name for this technique. "Doing Commits Like A Big Pile Of Laundry", perhaps?
  2. This is different from (and, imo, superior to) jj split:
    1. With split, if I miss a hunk that should have been in red, I have to split again and squash.
    2. This technique more easily allows sorting the easiest hunks at the beginning without worrying about how it will effect the commit sequencing.
  3. This reason why doing it all at the end is (often) better than using jj squash -i as you go is because the final state of the everything commit is guaranteed to not have any conflicts. Creating a new "fix red and green" commit and interactively squashing that into your red and green commits might break your blue commit if it happens to touch one of the affected files.
  4. A downside to this technique is that there's no guarantee that every commit will compile, which might be a dealbreaker.
Keep reading →

dithering with css

update 2026-05-8: hello hackernews - whoops, yes, i should have filtered out the noise specks. thanks to kevinsync for the reminder.

this is not a power or bandwidth saving technique à la low tech magazine. it's principally aesthetic.

handy if you're running a site with pictures but you want everything to maintain a consistent aesthetic and colour scheme. might be better to just process the images and save them already dithered, but this way can be customized. (note that the original image is still there beneath the filter, and that's what gets loaded, so, again, this doesn't save bandwidth - but it also means users could vary the effect per theme or toggle it off)

Noise dithering

image
noise 0.90 1 2
mix (feComposite arithmetic) 0.00 0.75 0.35 -0.05
two-tone
Keep reading →

we gotta do something about the bots

I checked out Twitter for the first time in ages to see if anyone had been finding anything in the Epstein files

Which, holy shit.

I'd heard journalists describe twitter as the place where narratives hatch, but it was surreal seeing the process happen so quickly.

Scrolling deeper and deeper down, I was hypnotized, feeling like I was wrapping my own red yarn around thumbtacked headshots, drawing my own connections as to what it all meant.👨‍🍼

Little did I know, it's conspiracy all the way down.

Underneath any high traction tweet I saw, there would inevitably be 3 or 4 replies running interference on its narrative. One would discredit it. One would downplay it. One would distract from it. It's clear there's a bot campaign trying to astroturf the cycle, and whoever's behind it is trying to impede democracy.

Later in the week, on a YouTube video about changing the clawback mechanics for canada's old age security pension, someone claiming to have worked their whole life for their pension had left a much-upvoted comment that was opposed to the idea. I replied pointing out it would only apply to people over 65 earning more than 100k a year. There was some more back and forth - the research took me maybe 20 minutes. Once my replies started getting upvoted, the OP deleted the post and everything went away.

A youtube comments thread: 'I am a single 72 year old senior and worked for 52 years. I deserve every penny'
'Do you have an annual income above $100,000 before counting the subsidy?'
'Not relevant. If you worked your entire life and paid into everything, you've earned it.'
'There's already a 100% clawback on the OAS for people over the age of 65 whose incomes are higher than $148,451. Dr. Kershaw is proposing to lower that to $100,000 so that there's enough money to send to elderly Canadians whose needs are more dire.'

There's no way to verify this, but I really doubt @Jeanlz8zw is a real person.🪒

How much of our sense of reality is steered by strangers who want us to be confused, mislead, misinformed, or angry?

Can anything be done?

Something I've wanted for at least 10 years now is for governments to offer identity verification services for social networks.

Here's how it could work:

  1. When I apply for a passport, I'd also get an account at identity.gov
  2. When I sign up for a social network account, I can link it to my identity.gov account

Now, whenever I post something, I'd get a few affordances.

(These next two blocks are interactive.)

Then, when I go on YouTube, I can filter comments based on these 3 properties:

Require:
unverified EXPOSED: The REAL reason they want digital IDs 🧵👇
✓ human-abc There are some real privacy concerns worth discussing here
unverified lol this is dumb
✓ citizen-123 🇨🇦 As someone who voted for digital service reform, I support this
✓ human-xyz Interesting idea but who would run the servers?
✓ Jane Smith 🇨🇦 I've been researching digital identity systems — happy to chat

Now. There are lots of concerns any reasonable person would have.

Won't this just lead to mass surveillance?

Not to be defeatist, but we already have it.

We can avoid making it worse, though.📈

Zero-knowledge proofs are a cryptographic technique that allow people to make verifiable claims without revealing any other information about them in a way that can't be linked back to your actual identity in the government's registry.🧑‍💻

The technical details aren't the emphasis of this piece. Just imagine that the government made a billion unique stamps, and handed out one to each of its citizens at random. Each citizen could then stamp their documents, readers could verify that the stamp was authentic, but the government wouldn't know which stamp belonged to which citizen, or what its citizens were doing with their stamps.

That's the gist of the thing I want. It is not a centralized government database that tracks all of our online activity and links it to our real identities.

Won't this chill speech?

Possibly! Though I think it being gradated and opt-in drastically softens the extent to which it would.

We also need to understand that speech is currently already chilled, just in a different way. I've stopped replying to people on the internet because I've lost confidence that they'll respond in good faith.

I worry for the world in which no one is anonymous online, but I also look forward to the day that I can read an angry man's comment on bike lanes and know that he's an honest-to-goodness ignorant Canadian and not a troll for a national disinformation department or PR firm.

Won't people just sell their tokens or let AI agents use their signatures?

I think all signatures will have to have a persistent identity such that you can always view someone's posting history.

So you'd get a stable pseudonym like human-xyz that is attached to all your human-signed posts.

Your citizen and personal signatures would get different pseudonyms, so that they can remain separate. This would have to be per-platform, so that you could have a different identity on Substack than AO3.

e.g. these would both be you:

  • substack-citizen-123
  • ao3-citizen-456

Though obviously if you signed posts on both with your full name, anyone could connect your accounts.

This would allow us to filter/tag/shame people who sell their accounts if we see that they're spamming.

Won't semantic analysis break anonymity anyway?

Yes, probably.🕵️ If that becomes a real problem, I guess we'll get tone masking services that rewrite your original message to remove your idiosyncracies for the especially privacy-valuing people (who are probably already doing this)

Won't an opt-in system eventually become mandatory?

The scope creep objection. It starts out optional but norms develop that pressure us to sign everything and disregard anything that isn't signed - ratcheting society towards less anonymity.

This seems like a real possibility to me, but I can still see it happening democratically. A decade of experience with the system leads people to conclude that generally, this is a healthy norm that has been beneficial for society. Something that feels more like mandating seatbelts than Ring cameras becoming ubiquitous.

That's my idealism, though. Realistically, it's contingent on the government and society that's implementing this technology.

Won't this exclude people who can't get passports?

I have trans friends who can't return to the U.S. without getting their passport revoked now. They're fearing for their lives and freedoms, and it's incredibly tone deaf of me to propose that we further empower the state to identify them.

I guess I just bite the bullet on this one. My belief is that this US administration exists because we didn't have this system in place in 2015, allowing the US political discourse to get hacked by Russians on social media. Obviously there were lots of other factors, but it seems quite plausible to me that adverserial nation states tipped the balance in what was an extremely close election.

I wish the world was safe for everyone. I wish that all our governments were trustworthy and progressive, but they're not. We need to find approaches that can help incrementally improve them.

This proposal seems unlikely to happen by default, and I would really strongly oppose a system that would forcibly de-anonymize everyone on the internet. My hope in writing this post is so that people who oppose online identification systems have something else other than a total rejection of the idea to point to when they raise their concerns.

Why does a government need to run this?

Big political science question! What is the role of government? Can it be trusted? Do we need a semi-adverserial relationship between the public and private sectors to guard against creeping state totalitarianism?

I don't have a conclusive answer to this, but I do know that New Zealand's RealMe is really good. E-Estonia is really good. Governments can build digital services that are actually well-designed, and write legislation that gets the private sector to adopt it.

Why does it have to be centralized?

Can't we just write an open standard and allow anyone to become their own identity provider?

I guess? That just sounds a lot more complicated to me. Imagine pitching that to your auntie:

"Ah yes, well this commenter is verified by OpenRep which is mostly for academics, but they're responding to someone who's verified by VeriCoin which is a pay-to-play scheme. They're probably a real person, but people on MeYou say that they've been compromised...

Isn't this what WorldCoin is?

Yes and I think there are tonnes of other ideas and groups working in this space:

  • GNU Taler for payments
  • European Digital Identity
  • eIDAS / EUDI Wallet
  • W3C SSI Standards

Some of these are state-based (eIDAS), private (WorldCoin), or standards-based (W3C SSI).

Vitalik Buterin has written a good survey of the general problem (in response to WorldCoin specifically):

So there's a lot of activity here. Hopefully we get more applied implementations to learn from.

Okay that's enough

In all likelihood, the first implementations of systems in this space will be flawed and privacy-violating.🤡 In the US and Canada in particular, when has any government service been on-time and best-in-class?

Nevertheless, I'm not giving into defeatism here. I'm completely energized by the potential upsides of an internet that has trust again. This is all the more important in our AI-suffused world.

Public sentiment towards technology and governance is in a trough, maybe rightly so, but technology and governance is also one of the best things we've got. We need to save it from ourselves to save us from ourselves.


👨‍🍼 I'm fearful that there's no institution or court high enough to handle this case anymore. I want democracy to prevail: media coverage → public outrage and mobilization → political mechanisms to legistlate an independent investigation, but key parts of the system seem kind of busted to prevent that from happening. Either Twitter's algorithm has radicalized me, or Fox News is seriously underreporting Trump's involvement with this man, and the files' suspicious absence of correspondence between them. Knowing epstein and going to his parties wasn't illegal. Maybe you just went to one of them with a bunch of 18-year-old prostitutes, so what can a court really do? Tut-tut them and tell them they should have known better? There is a clear wrongness in the activies of so many people in this network that seemingly are only suffering repuatational damage because we have nothing better at the moment. The scarier stuff is in the geopolitical power this network hints at the outlines of. Was he Putin's fixer? Did he build kompromat on the world's leaders to destabilize the postwar liberal world order with Steven Bannon and Peter Thiel? Did he have connections to Mossad? Is he still alive? What does "pizza" mean?? I really hope we one day find out.

🪒 Occam's razor on "a 72 year-old deleted their comment once it was falsified" vs "All The Details Of The Sockpuppet Industry" feels pretty painful to call, but also like, come on.

📈 Separately, of course I think we should also abolish the advertising-NSA surveilance state, especially now that LLMs are allowing the state to actually exploit this data, but it is separate.

🧑‍💻 I'm not a cryptography expert, but here's a somewhat technical primer on the concept.

🕵️ The way you write is like a fingerprint that language models can identify. Word choice, sentence structure, phrasing. These things can all be analyzed to figure out that human-123 is the same person as citizen-abc

🤡 Look at Discord (and its subsequent besting by photos from Garry's Mod.)

how i made a spoonerism generator

Part of my family’s canon is Spooner or Later, by Paul Jennings, Ted Greenwood, and Terry Denton.

The cover for the book Spooner or Later. It has a golden border with the title and authors set prominently. In the center is a cartoonish illustration of a man in a bowler hat.

A spoonerism is a kind of wordplay where you can swap the starting sounds of words in a phrase to make another valid phrase.

For example, “shake a tower” becomes “take a shower”.

It’s like dyslexia for fun.

Spooner or Later was that plus I spy.

An open book. In the center is an illustration of a fat man running. Around the edge of the page are many smaller illustrations, one of which is a rat flexing its muscles.

“Fat Man’s Race” (the captioned & skewed illustration in the center of the left page) becomes “Rat Man’s Face” (the image on the border of a costumed rat flexing his muscles.)

This book was a measuring stick of my cerebral function. By the sage of icks, I could only solve pimple suns at the start of the book, but after months of prayerful cactus, I was eventually able to finish the hack’s bard puzzles.

Solving them becomes pretty straightforward as your vocabulary expands, but creating them is a whole other skill.

The most straightforward way to do it is to think of two words that rhyme, then think of two other words that rhyme that start with the same sound as the first two words.

Word and Nerd

Whoa and Know

Know word? Whoa, nerd!

In my experience, it's difficult to think of the second pairing, let alone a pair that make sense together. It's a thing our brains aren't particularly good at, because it requires a kind of exhaustive search through the dictionary, and often you find a pairing that isn't grammatically equivalent like... “lead bike” and “bedlike”.

But if we could programmatically generate hundreds of pairings at once, we could scan and pan them quickly.

Sounds like a cob for... jomputers?... A mask for tachines?

Whiting rode for a pool that tears curds

A charcoal illustration of a knight atop a horse. Before him lies a pond of cheese curds, signposted: 'The Magickal Pond of Mozzarella'

The key is to use the CMU Pronouncing Dictionary filtered down to common English.

That gives us a list like so:

BALL  B AO1 L
TELEPHONE  T EH1 L AH0 F OW2 N
COLD  K OW1 L D
TALL  T AO1 L

We can split each of these words into its onset and tail. The onset is all the sounds before the first noun sound and the tail is everything after.

So, the onset of ball is B and the tail is AO1 L.

Quiz: what's the onset of cold? Runny nose. Ha ha, just joking. It's K.

Grouping words by their tails gives us a list of words that rhyme.

{
    "AO1 L": [
        { "word": "ball", "onset": "B" },
        { "word": "tall", "onset": "T" }
    ]
  }

Ergo, tall rhymes with ball Q.E.D. Behold the awesome power of programming.

Then we just need to find other words that rhyme and begin with B and T.

We go through our tail map and filter it down to entries that have the onsets B and T in them.

    {
    "IH1 N": [
        { "word": "bin", "onset": "B" },
        { "word": "tin", "onset": "T" }
    ],
    "ER1 N IH0 NG": [
        { "word": "burning", "onset": "B" },
        { "word": "turning", "onset": "T" }
    ]    ,
    // etc...
}

And with this data structure at hand, you have everything you need to make a nice little UI to find spoonerisms. Most of them will be trot hash, but as Napolean said, bots of lad is like a grit of bait.

That's what I found with my finder, at least.

Some (I) might argue that the best path to making spoonerisms that are actually delightful is to chisel them out with a thesaurus and rhyming dictionary, but for rapid ideation, this approach gives a reason t' doubt.

making a grainy spotlight effect with CSS

I first saw this on Hakushi Hasegawa's website1 and after a little digging around in the source, saw that it was implemented with a shader and three.js

But I had a hunch that it might be possible with CSS blending modes. Not because I fully understood them, but just because it seemed like the sort of thing they're made for.

And as you can see, my hunch was correct, but as I write this sentence I still don't completely understand why.

They've been explained a million times by other people, but it's an open secret that blog posts like this are for me, not you, so here we go again:

What actually are blending modes?

In a black and white image, every pixel can be represented as a value between 0 and 1.

0 is fully black. 1 is fully white.

Blending modes are rules for what should happen when you combine two of these numbers. For instance, the darken mode takes the lower of the two.

Or the multiply blending mode, which can be written as:

A × B = C

where

  • A is the base pixel value
  • B is the blend pixel value (the one on top with the blending mode set)
  • C is the output

If you have one pixel that is 0.5 (medium gray) and another that is 0.7 (light gray) and you blend them together with the multiply mode, you'll get a pixel that is 0.35.

×
=

And to be clear, this isn't just 3 divs with 3 different hexcodes. The third square you see is two divs stacked on top of one another with mix-blend-mode: multiply set on the top one.

need proof?

One thing you might take away from multiply is that it's not possible to get a lighter value than the values going into it. A fraction of a fraction is always smaller than the fraction itself.

How about the other blending modes? Expand for more details than the average person wants to know

f (
,
) =

I also plotted each of the non-conditional modes in desmos. It looks a bit like a modernist painting, but might also help you if you take the time to understand it.

Using blending modes as masks

So how do we use these tools to make the spotlight? The key is understanding that the abrupt edges of the color-dodge mode can turn a gradient into a mask.

C = A1−B

With color-dodge, as the blend pixel approaches 1, the denominator approaches zero and the output value increases abruptly.

Here's a medium gray base layer and a black-to-white gradient blended over it using color-dodge

Three squares side-by-side. The left square is gray. The middle square is a black-to-white gradient. The right square is the result of color-dodge blending the two: a square that is lighter gray on its left half and rapidly shifts to white at the midpoint.

Think about what's happening here.

A is 0.5

B ranges from 0 to 1.

On the left, B is 0, so we're dividing 0.5 by 1, which equals 0.5 2

As B approaches 1, we start to divide A by a smaller and smaller number. When B is 0.5, we divide 0.5 by 0.5, which equals 1: pure white. Any higher value of B makes no difference because 1 is the lightest value possible.

As soon as the blend pixel becomes lighter than the base pixel, the output flips to white.

What if we blended a gradient over a noise texture?

Three squares side-by-side. The left square is a grid of pixels, each a different shade of gray. The middle square is a black-to-white gradient. The right square is the result of color-dodge blending the two: a square where the left side is mostly dark gray pixels, and as you move to the right, more and more pixels become white, until the right side is almost entirely white.

Some of the noise pixels have slightly lightened, and many of them have turned fully white.

Effectively, what's happening is the computer is sampling each pixel and comparing it to the gradient. If the gradient is lighter, it hides the pixel. The further along the gradient you go, the more pixels get hidden.

Now all we need to do is make the remaining pixels black. Fortunately, there's another blend mode for that: color-burn

C = 1− 1−AB

color-burn takes the inverse of your base layer, divides it by the blend layer, and then takes the inverse of that.

Let's color-burn a black layer over the color-dodge layer.

Three squares side-by-side. The left square is the result of the previous color-dodge example: gray pixels randomly scattered on the left. The middle square is black. The right square is the result of color-burn blending the two: a square where all the non-white pixels have turned black.

We start by picking one of the remaining visible pixels. Say we pick one that's dark gray (0.3)

  1. The inverse of dark gray is a light gray (0.7)
  2. The blend layer is black (0)
  3. 0.7 / 0 = Infinity
  4. 1 - Infinity = -Infinity

CSS can't display negative infinity: it clamps the value to 0. This is why all the gray pixels turn black: we're always going to be dividing them by zero.

But there's a special case! What if the pixel we sample is white? (1)

  1. The inverse of white is black (0)
  2. The blend layer is black (0)
  3. 0 / 0 = NaN
  4. 1 - NaN = NaN

NaN means "Not a Number", which is a special value used to represent undefined or unrepresentable numerical results. Fortunately for us, CSS renders these specific errors as white.3

And so there you have it. There's some artifacting going on due to tolerance for near-white values, but to recap:

  1. color-dodge "hides" pixels that are darker than the gradient.
  2. color-burn blackens the remaining pixels

This gives us a pixelated falloff based on the lightness of a gradient that we can overlay atop a noise layer.

Building the effect

Now that we understand the principle, let's build it step by step.

First, we create a div and center some text in it:

Then we place a new div above the text, with a noisy background image. Statically positioned elements' backgrounds paint first, which is why the absolutely positioned text is shown above it.

Then we place a div inside the noise layer and give it a radial-gradient from white to black.

Because the div is absolutely positioned, it's now painted above the text. We can set a darken blend on the darkness layer so that the darker black of the text shows through.

Now we can add mix-blend-mode: color-dodge to the spotlight div.

But wait, why doesn't the spotlight hide the text? Isn't white lighter than black?

Here's our layer structure:

<div class="container">
  <div class="text"> <3 </div>
  <div class="darkness">
    <div class="spotlight"></div>
  </div>
</div>

Nesting spotlight inside the noise layer puts it in a new stacking context.

When you set a blend mode on an element, it only blends with elements that are behind it in the same stacking context. To use a metaphor, the spotlight is inside a box (the noise layer). It can blend with the walls of its box, but it can't reach outside the box to blend with things sitting next to the box.

This changes how the box looks, but the box (the noise layer) is still blending with the text in its stacking context using darken.4

Finally, we add a new div over everything, with mix-blend-mode: color-burn and background: #414141 to turn all the remaining non-white pixels black.5

A little JavaScript to make the circle follow the mouse and you've got a spotlight!

If you'd asked me how blending modes worked before writing this, I would have said "by iterating through them until you find one that looks right."

I think that's still kind of the case. Experienced photographers probably have a stronger intuition about when to use a particular mode for adjusting one of their photos, but I'd be surprised if most of them don't also just tap the down arrow on the selector to preview what each of the modes would do.

And I could have implemented all of this with canvas and it would likely perform better, but I'd need to have learned the theory regardless, and it's cool to learn what's possible with regular CSS nowadays 🙂


1 If the website's down / changed, I recorded a video

2 So color-dodge can't make a value darker than the base pixel. That's good to know.

3 This is a happy coincidence and different graphical softwares handle this case differently. color-burning black over white in Affinity Photo turned everything black (though #010101 worked fine.)

4 I hope this makes sense. At a certain point you just need to try and make something yourself so that it clicks.

5 For this particular noise texture, I found that a dark gray looked better than pure black.

phones are about to get more useful

Two years ago, a colleague asked me "concretely, what's a way in which you think AI is going to change the world?"

I said: the way we relate to and use software. We will stop writing or installing it.1 We'll instead have an AIs that dynamically write our programs for us.

Two years later, I'm feeling even more confident about this. Claude Code and Codex are the worst they'll ever be, and they're already one-shotting simple webapps.

Check out this ~special~ metronome I asked Codex to make.2

I wanted it for months, but never got around to making it myself, because to code it by hand would take more time than the idea was worth. But now I can get it made in 30 seconds, provided I have my laptop on me.

The next step could be making it possible to create these apps by typing a prompt on my phone: "Write a program that does X, Y, and Z."

The model writes it and stages it somewhere. I'd maybe ask for a few iterations, and once satisfied, deploy it on my site.3

I think this kind of capability would be really useful, and I'm sure the ergonomics will improve rapidly (alongside their capabilities and possible complexity, incorporating our phones' cameras and gyroscopes and what have you.)

For now, it remains a niche power among those who are somewhat technical or tool-making. But it seems kind of easy to commodify and market: what could be more useful than making it easy to make useful things?

Interoperability will be important, and there will still likely need to be some organizational effort to design and spread standards so that my bespoke apps can interact with other people's bespoke apps.

But, basically, we're all going to become Geordi Laforge, Chief Prompt Engineer, and everything will go great. There will be no problems.

Keep reading →

blocking youtube recommendations

youtube i love you, but you're bringing me down

in a bastardized interpretation of fitness landscapes, i've recently been thinking about how every decision i make is a step in some direction in an infinite mountain range. in this space, my altitude isn't environmental fitness. it's something more like "personal value" or "amount self-actualized" where you can imagine standing at the peak of a hill in this endless mountain range as being highly satisfied with myself, highly engaged in my life.

Keep reading →

data wrangling shootout

Say I have a CSV with two columns:

country, users
China, 113
Nicaragua, 29
Jordan, 89

and I want to aggregate these by continent. What's an easy way to do that?

Well I found this country-by-continent dataset by samayo:

[
  {
    "Country": "Afghanistan",
    "Continent": "Asia"
  },
  {
    "Country": "Albania",
    "Continent": "Europe"
  }

So I have everything I need except for a workflow. Let's evaluate some options.

  1. ChatGPT
  2. nushell
  3. DuckDB
  4. Observable
  5. TypeScript with Bun

ChatGPT

I attached the two files and submitted the following prompt to ChatGPT 4o:

Keep reading →

← All tags