A Portrait That Listens to My Spotify and Judges Your Browser
Written September 27, 2026, about 16 minutes to read.
If you open the home page now, the portrait doesn’t just sit there anymore. It says hi, then puts on whatever the song I’m listening to deserves, then takes a good look at you and tells you what it thinks. Then it does the Omarchy bit it always did. Every couple of seconds, by itself.
A made-up visit: someone in Germany, on Windows, at 2 am, while I play arabesk.
I built all of it with Claude Code, over one long Sunday, mostly from my phone. This post is how it got there. It started with the Spotify widget in my footer breaking on a Sunday morning.
Spotify on my footer
Back in March I added a small “now playing” thing to the site: the footer shows what I’m listening to on Spotify, with the album cover turning like a record, and there’s a whole page for it too. The site is static (Hugo, GitHub Pages, Cloudflare in front), so the Spotify part lives in a Cloudflare Worker. The page asks halilk.com/api/now-playing, the worker asks Spotify with my refresh token and answers with the track.
It worked for six months and I never thought about it once.
Two songs at the same time
Then one morning the footer on my laptop showed one song, and the same page opened from Instagram’s in-app browser on my phone showed another one. Neither of them was what I was playing.
It was two problems at once:
- Spotify changed the rules this summer. Since July a refresh token dies six months after you approve the app, no matter how often it’s used, and mine was from the end of March. Spotify’s answer was literally “Refresh token revoked”.
- A few days earlier we had made the worker remember the last track it saw, so the footer would still have something to show when Spotify misbehaves. But that memory lived in Cloudflare’s cache, and that cache is per data centre. My laptop goes through Warsaw, the phone went through somewhere else, and once Spotify stopped answering each data centre kept showing whatever it had seen last. Hence two songs.
The second one is fixed with a Durable Object: one small object with its own storage for the whole world, and it’s on Cloudflare’s free plan. Every data centre asks the same object now, so everyone sees the same song.
There’s no way around the first one. Twice a year I have to say yes to Spotify again, and I was never going to remember that. So the worker checks the sign-in every Monday morning and two weeks before it runs out it emails me a link: one tap on my phone, Spotify’s consent screen, done. For the email, Cloudflare took over the halilk.com mail from GoDaddy ([email protected] still ends up in my Gmail). The link works once, for 30 days, and only for my Spotify account. For anyone else those pages are a plain 404. Before shipping it I had a second agent do a security review of the sign-in flow. It found five things worth fixing, which is exactly why you do that.
Making the portrait react
The portrait is a painting of me in front of a blue and white mosaic. Until now you had to tap it: “btw I use Omarchy”, then “deal with it” with 8-bit sunglasses and a cigarette dropping onto my face, then it flips over to the photo it was painted from. The sunglasses were my favourite thing on the whole site.
So, what if it reacted to the music? Arabesk plays, I get a moustache and a rain cloud. Jazz, a beret. And while it’s at it, what if it said something about you?
I gave Claude (Claude Code) the rules:
- the first thing you see is the original painting, then the music, then you, then “btw I use Omarchy” with the shades, then the old lines
- it plays by itself, a new line every couple of seconds, and a tap skips ahead
- on the about page it keeps quiet, people are reading there
- no video, all code, like the shades
- light on the phone, the thinking happens on the server
- funny, passive aggressive, a bit dark
What kind of music is this?
I assumed Spotify would just tell us the genre. It used to, every artist came with a list of genres. Since February 2026 small apps (the “development mode” kind, which mine is) don’t get that field at all anymore, and things like tempo and energy were already taken away from new apps in 2024. There’s no permission to ask for, it’s just gone.
So the worker asks three free catalogues instead and remembers the answers: MusicBrainz for the artist (community tags like “anatolian rock”, “arabesk”, “uzun hava”), Deezer for the album’s genres, and Apple’s iTunes search in the Turkish store, which knows the difference between Arabesk, Sanat and Halk. Apple rate limits Cloudflare’s shared servers though, so from the worker it mostly says no. Deezer and MusicBrainz carry it.
Then a few rules turn that pile of words into one of about thirty moods. Best bug of the day: Deezer files the album of a Turkish indie song I had on repeat under “Indie Rock/Rock Pop”, and the K-pop rule found “k pop” hiding in “rock pop”. For a few minutes my portrait was dressed for K-pop.
More pictures of me
For the portrait to change with the mood it needed more than one picture. I dropped 25 photos of me into a folder: selfies, one at 3 am in bed, me in a Knicks jersey in a hotel room, me dressed as an Ottoman sultan (don’t ask), a few in sunglasses, and the one with the Microsoft lanyard the original painting was made from.
Every photo went through the same steps: find the face (478 landmarks with MediaPipe), scale and rotate the photo so the eyes land exactly where the original painting’s eyes are, and cut me out of the background. Eighteen made it. The blurry ones, a floating head and a couple of duplicates didn’t. Because every one has the eyes in the same spot, switching between them looks like the same person in the same frame, just in a different mood.
The first round painted them with the same recipe as the original portrait:

My verdict: not very artsy, and some of the source photos are so bad it’s unbearable. A painting can’t hide a blurry 2024 phone photo. Pixel art can. So everything except the original became pixel art. We tried a few sizes (120, 96, 80 and 64 pixels across) and a few outlines (black was way too sharp), and settled on 80 pixels, 18 colours, skin tones matched to the original painting so they look like one family, my own eyes sharpened before shrinking (no drawn-on eyes, those always end up creepy), shadows a little cooler and highlights a little warmer like hand-made pixel art, and an outline that’s just a darker shade of whatever it touches, lighter on the side the light comes from.

The first batch came out grey. Shrinking a photo to 80 pixels mixes every pixel with its neighbours, so skin blends into stubble, a sweater’s knit turns to mush, and picking 18 colours out of that pulls everything toward beige. My review of one of them was “Colors looks so bad and washed out on this one. Ewww”. Fair. Now the palette gets its colour and contrast back at the very end, the dull colours more than the strong ones, so a red t-shirt doesn’t turn into a traffic light. I picked the strongest of three versions.
Each one is about 8 KB. They’re stored ten times bigger than they are, with hard edges, because browsers smooth images that sit on an animated layer even when you ask them not to. The sultan is for Turkish classical music, the Knicks jersey for hip hop, the flannel for indie and country. Me looking down at the camera from under the Space Needle is for when you’re on Windows.
The props, three times
The first props were pixel art drawn by hand, one character per pixel, like the sunglasses. They looked like this:

My feedback was short. “Wig looks stupid.” “I’m expecting better quality.” And then a bit more useful: the original painting with the pixel shades and the cigarette is the anchor, the props should feel like they belong next to those, and the whole thing should look like it was made by someone who’s half engineer, half art student. Pixel art next to the paintings is this site’s thing anyway (see the ghost on the projects page).
So the second round is sculpted. Every prop is a small 3D model written in code as signed distance functions: the cowboy hat is an ellipsoid with a crease and two pinches on top of a curved brim. The model gets lit from the upper left like every painting on this site, rendered, and then pixelated: the colours cut down to about a dozen, the edges darkened a little, and the brightest highlights turned into pure white pixels, the same kind of glint the deal-with-it shades have. (Dead Cells did its characters in a similar way, 3D models rendered into pixel art.)
There were more than 70 of them by the end of that round: hats, glasses, moustaches, a tespih, a çay glass, a disco ball, a penguin for Linux users. Plus a few screen things drawn by hand, like the Windows hourglass and a PowerShell window. They’re on nearly the same pixel grid as the pixel faces, so a hat and a head look like they belong to the same game.
Each picture knows where things go on it: the eyes, the mouth corners, the chin, and the outline of my head at every height. A hat’s band gets pinned to that outline at the hairline, glasses to the pupils, a moustache to the corners of the mouth. Hats are allowed to stick out of the circle, and they grow until they cover my hair, because on my phone a beret was sitting on top of my hair instead of on my head (“so shitty placed”, in my words). The nazar was the one prop I liked from the first round, and it still sits in the empty corner of the mosaic like it does on every door back home.
Then I looked at them on my phone, next to my face, and they were still bad. Country, jazz and hip hop especially: a cowboy hat the size of a mushroom, a beret like a black pancake, a trumpet sticking out of my mouth like a gold arrow. My feedback was “Omg how are any of those acceptable to you”, and “be a bit more minimalistic”.
So, round three. The shapes stay 3D, but each prop is baked flat now, the way the deal-with-it shades are: every material in its own colour with three tones (shadow, base, light), at the shades’ pixel size. And one prop per genre, not a pile. A moustache for arabesk, a cowboy hat for country, a black cap for hip hop, a visor for electronic music.
Some props didn’t survive me at all. Tears, for one: the idea was bad. A little blue sweat drop for Windows users went with them, because it looked like a tear too. So did the rain cloud, the trumpet, a gold grill, and a beanie that looked like an onion.
Instruments don’t sit on top of anything any more either. A double bass standing next to me looked cheap, so I asked for the saxophone to go into the tiles. Now jazz lays a sax into the mosaic in small gold tiles with grout between them, under my picture, where it can’t cover me. A guitar does the same for folk and blues, a violin for classical.

The props also learned to stay out of the bubble’s way. On a phone the bubble sits left of my face, so everything that stands on its own goes on the tiles to my right. And after it once covered half my face (“how is this passing your quality check”), every line now comes with how far left my head reaches on that picture, and the bubble stops before it.
The mosaic is a screen
The mosaic behind me is about 900 tiles, and every one of them can change colour. When the music part starts they become the album cover. Then they take the colours of the genre, rain grey with a bit of red for arabesk, navy and brass for jazz. When the portrait looks at you a radar sweep goes round, then the tiles turn into your flag, and later into a clock showing your time. Your flag stays up for the whole bit about you; there are 187 of them, and nothing is dimmed at night, bright tiles on the dark page look better. They can also be the sea, smoke, or a green portal, more on those below.

That’s the first real one, from the Sunday I finished it, and it got something wrong. The song was Deniz Tekin, the catalogues tag her “folk”, and my rules turned Turkish folk into türkü. So she got a moustache and a joke about Turkish folk songs always being about a mountain. She’s a singer-songwriter with a guitar, not a bağlama. (The visitor after that, from Warsaw on an iPhone whose browser speaks Turkish, was me.)
Two favourites
Fixing that made it personal. Deniz Tekin is my favourite Turkish artist, together with Duman, so the two of them get their own bits instead of a genre.
Deniz means sea in Turkish, and this whole site is a sea. When she’s playing, the mosaic turns into the same sea as the footer: white crests rolling through blue tiles in the day, and at night a navy sea with the moon and its gold road on the water. A little paper boat floats by next to my face. “deniz means sea. why do you think this whole website is the sea”
Duman means smoke. So when Duman is playing, the cigarette from the “deal with it” bit comes out early, on a picture of me in sunglasses and a striped shirt, and the tiles fill with smoke like a bar at 2 am. “duman means smoke. so the cigarette stays. sorry mom”

Songs I play a lot get a line of their own. “Geri Dönerim” means I’ll come back, and the portrait says that it always does, to this song. For Duman’s “Köprüaltı” (under the bridge) it points out that it’s not the Red Hot Chili Peppers one, it’s the good one.
Say my name
The first lines Claude wrote read like fortune cookies. My feedback was “it’s too obvious that it’s written by AI. Be more human biatch”, and that Breaking Bad and Rick and Morty are welcome. So the lines were rewritten the way I’d text a friend: lowercase, a bit of Turkish, a bit rude, no neat sayings. “müslüm baba is on, pass me a cigarette and a tissue.” “i’m 40 and listening to türkçe rap. we move.” And for anything from 1986: “same as me. we both still work, mostly.”
After the Omarchy bit, someone else takes over for a moment. Breaking Bad: “say my name.”, “heisenberg.”, “you’re goddamn right.” on the picture of me in a black shirt and sunglasses, with a pixel pork pie hat and the tiles in Breaking Bad green and yellow. I also asked for the police sketch of Heisenberg from the show, drawn as me. Turns out there’s no police sketch: the drawing I remembered is the one the Cousins pin to a Santa Muerte shrine at the start of season three. A pencil drawing of me like that got made. It looked bad. The hat on the pixel me is better.
Rick and Morty went a similar way. Drawings of me as Rick and as Morty were awful, so the show comes to visit instead: the tiles open a green portal and Rick himself stands next to me (“wubba lubba dub dub”), followed by Morty, Pickle Rick, Mr. Poopybutthole or a Meeseeks. They’re drawn by hand, pixel by pixel, because at that size a sculpted face is mush. “i’m mr meeseeks, look at me! someone asked me to center a div. existence is pain”

A creeper face from Minecraft was in there for about an hour too. Not that original, it’s gone.
And philosophers, because why not. Each lap one of them says something and I answer. “schopenhauer: life swings between pain and boredom. i built a website that covers both.” “camus: one must imagine sisyphus happy. he had a personal website too.” “sartre: hell is other people. nothing personal. ok, a little personal.” And Mevlana’s “come, whoever you are”, even on Windows.
Now, you
Everything it knows about you, every website gets anyway: Cloudflare knows roughly where the request came from and what time it is there, and your browser says what it is. Nothing is stored, it’s used for one reel and forgotten, and the portrait tells you so at the end: “anyway i don’t store any of this. i forget you the second you leave. like everyone else does”
Some of what you might get:
- On Windows the tiles turn PowerShell blue with a spiral going round and round and a few red errors, and you hear that somewhere an AI agent is on its 38th PowerShell attempt. For you. Or that your agent is still trying to escape a path with spaces in it, three hours in. If you’ve ever watched an agent do that on Windows, you know.
- From LinkedIn, a green #OpenToWork ring around the mosaic. “linkedin?? i’m not open to work. the ring is a joke. mostly”
- With Global Privacy Control on you get no flag and no distance, but you do get a tinfoil hat. “respect. i made you a hat”
- From France: “bonjour. no, i’m not wearing a beret for you”. Next line: “…ok fine”. The beret drops, with a baguette.
- From Australia the whole thing turns upside down. “g’day. i flipped myself upside down for you. you’re welcome”
- At 2 am your time: a nightcap, and “go to sleep. seriously. i’m a website, i have to be here, you don’t”
- A Turkish browser outside Turkey: “gurbetçi? hoş geldin kanka”
- From Warsaw: “we’re like 10 min apart. don’t come over”

Lines about you are masked in session recordings, and screen readers only hear the lines you ask for, not a new one every three seconds.
What it’s made of
Claude Code wrote the code and most of the lines in one long session, mostly while I was on my phone. I pointed, asked for things like the Windows agent joke, and complained until the props looked right. When there were several things to try at once, like the Heisenberg drawing and the painting experiments, it handed them to helper agents and kept working on the code in the meantime.
- Cloudflare Workers with one Durable Object, on the free plan, and Cloudflare Email Routing for the reminder emails
- Spotify’s Web API, plus MusicBrainz, Deezer and iTunes Search for the genres
- MediaPipe for the face landmarks, rembg to cut me out, OpenCV for the paintings and the pixel art, numpy for the tiny 3D renderer
- Hugo for the site
- Playwright and headless Chrome for the hundreds of test screenshots, with the Spotify API stubbed out (a round of testing got the site rate limited by Spotify once, never again)
- ImageMagick and ffmpeg for everything in between
Come back later
The reel is written fresh for every visit and it changes with whatever I’m playing, so the portrait you get tomorrow probably isn’t the one you got today. If you catch me in a cowboy hat, I’m not going to explain.