The works page got a real gallery, a real modal, and RAW files support
(Non-relevant random image)

I went back into the works page wanting to fix one small thing and ended up rebuilding half of it. Here's what actually happened.
Project cards open in a modal now
The work/project cards used to just be a link out to GitHub or wherever. Click it, you leave the site. Fine, but it meant there was nowhere to actually say anything about a project beyond a title and a date.
So now clicking a card opens a detail panel in place, copied off Apple's own product page pattern (the full-screen panel their pages open from buttons like "Immerse yourself in Ultra Retina XDR"). Each one shows the actual tech stack as icons instead of plain text, and I went back through every project and contribution and wrote real descriptions instead of the placeholder one-liners that had been sitting there since forever. The "Visit project" link at the bottom now shows the GitHub logo or a globe depending on where it actually points, instead of a generic arrow that didn't tell you anything.
Building that also surfaced a real bug that had nothing to do with the modal itself: the light/dark toggle was a separate useState in every component that used it, so flipping the theme from the navbar didn't actually propagate anywhere else. Never noticed because most things on the site don't care, but the modal's text was rendering in the wrong color scheme against its own background half the time. Fixed it properly with a shared theme context instead of patching around it locally.
Also swapped every alert() on the site, admin panel included, for actual toast notifications while I was in there. Small thing, but "confirm your browser's ugly native alert box" was not a good look in 2026.
Then a real photo gallery
Separately, the works page gallery used to be five hardcoded local images. Wanted to be able to add photos without touching code, so that's a real gallery_images table now, managed from its own admin page: multi-upload, inline alt text and captions, a visibility toggle so a photo can come down without deleting it, drag to reorder.
"image/x-adobe-dng is not allowed"
First real test of the new upload flow was straight from my phone. iPhone shoots ProRAW, so the file was a .dng, and it just bounced with image/x-adobe-dng is not allowed. Fair, my server only knew about normal JPEG/PNG/GIF/WebP.
So then it's the question of what to actually do about RAW. There's a cheap option: RAW files usually have a full embedded JPEG preview baked in, so you can just pull that out and call it a day, no real decoding needed. And there's the expensive option: actually decode the RAW data yourself. I went with the expensive one, mainly because the embedded preview isn't guaranteed to be full resolution, and I didn't want the gallery quietly serving a worse copy of a photo depending on which camera took it.
Ended up using libraw-wasm, which is LibRaw (the actual RAW decoding library basically everything uses) compiled to WebAssembly, running in a Web Worker. Whole thing happens in the browser: decode the RAW data, demosaic it, draw it to a canvas, export as JPEG, and the server never has to know RAW format exists at all. Tested it against a real 20MB DNG off my phone and it worked first try, colors looked right, full resolution, about a second to decode. Added support for the common non-Apple formats too while I was in there (Canon, Nikon, Sony, Fuji, Panasonic, Olympus).
Then the production build just... hung. Forever. No error, no progress, just sat there. Took a bit of bisecting to figure out it was Turbopack choking on bundling a Worker plus a WASM file together, something it apparently can't handle yet. Webpack handled the exact same code in about 4 seconds. So dev still runs on Turbopack, but the production build specifically pins Webpack now because of this.
Stealing from Apple's marketing websites 🫣
I also rebuilt the gallery itself as an auto-advancing carousel instead of the old bespoke scroll setup, and got kind of obsessed with matching Apple's real entrance animation for the play/pause pill and dot navigation. I wanted it to actually feel like their site, not just "inspired by," so I reverse engineered the real thing off apple.com, timing curves and spring values, straight from their shipped JS instead of eyeballing it.
Took way more iterations than it should have. Fixed the icon getting visibly squished during the entrance (turns out the real button element never has a transform on it at all, only the background chrome behind it does, so I had to split mine into separate layers to match). Found a bug where the controls were visible before they should've been, because an earlier timing measurement had accidentally started sampling mid-animation and made me think opacity never went to zero. Then a glitch where the intro circle and the pill it turns into would overlap for a frame, fixed by tightening the handoff instead of just delaying things, since delaying it killed the glitch but made the whole thing feel slower than Apple's, which defeated the point.
Genuinely spent more time on this than the RAW decoding, for a detail most people will never consciously notice. Worth it anyway.
Smaller stuff, same cleanup
Reordering photos in the admin panel used to let you drag from anywhere on the row, which meant scrolling the list on my phone kept accidentally starting a drag instead. Now only a small grip handle on the left starts the drag, everything else just scrolls normally.
New uploads used to go live on the works page immediately. Now they default to hidden until I actually review them and flip them visible, so half-captioned photos don't show up the second I upload them.
Anyway. Started this wanting to fix one small thing on the works page. Ended up with lots of cool stuff. On to the next thing.