INTRODUCTION
a while back, i came across chaski on TikTok, who has been collecting Wikipedia images and their captions in an are.na channel called wiki poetics since 2021 (!!!). out of context, these captions can be strange, poetic, and often accidentally profound, but also just simple descriptions of everyday objects. the TikTok comment sections were also full of people relating to them, with one comment in particular saying it felt like an "Earth loading screen".
i wanted to try developing a similar page and growing my own collection. the crux of the project is actually very simple, as it's more exploratory than anything else, so the real question for me was how i wanted to present that information. the phrase from earlier kept lingering - i tried a video game style mockup, but i quickly realized two things:
- the quality of the images is not consistent (e.g. lower quality images look terrible that blown up)
- the beauty also lies in the specific Wikipedia captioned image formatting itself
given that the content could span such a wide range of topics, regions, and eras, i decided on an infinite canvas, almost as if the user was travelling through space and time.
here's what i determined:
- omnidirectional travel (left-right and forward-backward through the use of parallax)
- a pipeline for collecting Wikipedia figures with their captions, sources, and licenses
- distance-based atmosphere (things far away should be small, hazy, and out of focus, while things closer should draw your attention)
DAYS 1-2
based on the are.na link, it looked like all the images were screenshots (although i will say they were very standardized if so).
i didn't mind doing the same approach, but i knew i couldn't guarantee the same border every time. i opted for a more automated process, using code to create the same box that Wikipedia does.
this also implied a more automated way of pulling the metadata for the image.
every figure starts as the URL you get when you click an image in a Wikipedia article β the one with "#/media/File:..." in it.
from that single URL, the ingest script could potentially derive everything: the source article, the file title, and the caption, which it scrapes from the article's own <figcaption> (with fallbacks for gallery captions and infobox images).
the source image caption however, can differ from the article's image caption, so in those cases, i needed to manually include the caption i wanted.
since i'm re-hosting these images, i also made the script check each file's license via the API and flat-out refuse anything that isn't freely licensed (e.g. no fair-use). each accepted image gets capped in size, converted into WebP thumbnails, uploaded to Cloudflare R2, and recorded in a Supabase table β all keyed off the file's identity, so re-ingesting the same figure just overwrites in place. collecting was now literally just adding URLs to a text file and running a batch script to process the new links.
DAY 3-5
the infinite canvas was easy enough to implement, as there's a ton of semi-tutorials online already, but i needed real depth too (from the parallax).
for this, i deliberately avoided CSS 3D perspective and instead, every card's on-screen position and scale came from pinhole-camera arithmetic (scale = focal / (focal + depth)), computed in JS and applied as flat 2D transforms.
the world is just one big tile of card slots, repeated on a lattice sideways and cycled in depth.
which image appears in a slot is a hash that includes the wrap count. so, when a card passes the camera and recycles to the horizon, it comes back as a "different" image.
you can fly forever in any direction and never watch the same arrangement walk past (of course, you may see general image repeats, but i needed to avoid looped placements that make the user say "wasn't i just here?").
the hardest point of the project actually came pretty early on. i had placed the cards in such a way to not overlap in the world space, but they overlapped anyway. (οΌβΈα) it turns out disjointness does NOT survive parallax β two cards at different depths move at different speeds, so they eventually slide across each other no matter where they're placed. the only gap that actually survives every camera position is between cards at the same depth. i kept fine-tuning the discrete depth layers of the field, eventually landing on 12, with a packer that guarantees no overlaps within a layer.
DAY 6-7
the interaction felt pretty laggy, so i made some tweaks to adjust the speed and provide a smoother navigation. other interactions also felt a little clunky, resulting in the addition of more touch gestures: dragging to pan (move anywhere without changing depth) and click/tap to "fly" to an image. the latter was born out of my own laziness, but ended up being helpful - did something in the corner of your eye catch your attention? here's a faster way to get there.
i ran into a few minor issues here, one being that selecting an image occasionally resulted in the image positioned in front blocking the target. this was usually due to height issues (long captions or tall images). with some more geometry (yes, math again), i had the code recognize this instance and appropriately fade the card in the front.
DAY 8
at this point, i had only the image and the caption, but i lacked the mandatory attribution. this was easy to add luckily - by hovering over a card, an area under the caption is revealed, showing its source article and license. of course, on mobile/tablet, there isn't necessarily a hover, so this is resolved by pressing and holding the card (e.g. the panning feature described earlier). it's not the most graceful, but the gestures on those devices are much more limited, so this seemed like a compromise.
i added a similar corner panel on chaski's page detailing my inspiration and citing them. i created a new opposite corner panel that holds the navigation instructions. to better tie the page into the inspiration, i added a simple loading screen displaying "hello world" and the instructions. this also conveniently covers the blank moment before the field mounts.
DAYS 9-10
i spent the remainder of the days just finding more and more images. for this stage, i intentionally avoided cross-referencing chaski's collection in the spirit of the project. my collection is now currently over 1.5k at this point! it is super addicting. i also decided to expand on what qualifies as an image in this collection and i extended it to any image hosted on Wikipedia, regardless of if it was featured on an article page. of course, i still prioritize article images, but i saw so many with informal captions that i appreciated.
i also finished the final admin step of migrating my site DNS from Porkbun to Cloudflare: R2 only lets you serve a bucket from a custom domain if Cloudflare manages that domain's DNS. without the custom domain, there's no CDN caching in front of the images, so this was a step needed to serve the page.
FINAL THOUGHTS
it's a little funny that a page about the human experience demanded so much time buried in code and rendering math, but i think that's fitting β all of this exists so the experience can just be: drift, see, read, learn.
it was honestly my favorite project so far too, especially because it technically never ends. as i go about my day-to-day, i find myself making a mental note to look something up and read about it: i've already learned so much about so many random things i've collected in this short amount of time. there's always more to see and more to engage with and the way each of us do that will be different. i wanted a space that makes visitors slow down and look around, the way the collecting made me, and i think i was able to do that.
key takeaways:
- world-space disjointness does not survive parallax β plan your depth model early
- decoded pixels, not file size, are the real image budget on phones
much thanks again to chaski for giving me the go-ahead to explore this idea on my own! check out their work (ΰΉ'α΅'ΰΉ)βΈ*