Weird Web October 2024: A Retrospective
I meant to write this retrospective a couple of weeks ago, but between the US election and planning a cross-country move, I’ve been a bit busy. Still, never late than never!
Weird Web October (WWO) was an Inktober-style challenge put on by Jay Zuerndorfer. Participants were invited to make a website each day, based on a prompt.
I only found out about it on the 10th of October (from @latte’s Mastodon), so that’s when I started. You can see all my entries here.
In total, I completed 11 of the remaining 21 entries! I would’ve liked to have done more, but I got a nasty cold half-way through while travelling, and I never quite got back my momentum after that.
In this retro, I’m going to talk about my feelings on the challenge as a whole, and then talk about each entry that I completed.
A Website A Day Keeps the Boredom At Bay
All in all, I really enjoyed this challenge!
One of my biggest creative hurdles is finishing projects. It’s dispiriting how many half-baked projects I’ve abandoned over the years.
WWO forced me to get each website out in a day, no matter how janky or unpolished it ended up being. The constraint really helped me keep my ideas in scope. I might come up with some cool idea for a prompt, but if I didn’t think I could do it in a day, it was a non-starter.
While that could’ve led to boring, unambitious projects, I pushed myself with some of them, and am proud of what I accomplished!
I’m currently on sabattical, and I don’t think I could’ve finsihed nearly so many websites if I was working full time. When I was working as a programmer, I rarely had the mental energy left at the end of the day to work on my own projects. With that in mind, I’m really impressed with all those participants that made their websites while working. Y’all rock!
The WWO website used a web ring technology called Octothorpes to publish everybody’s entries on a public page.
All you had to do was add a little snippet of HTML to your site, visit it, and it would show up on the master list!
For example, here’s the list for prompt #10:
I really enjoyed looking through everybody’s entries each day and definitely took lots of inspiration from them.
Some of my favorites were:
- ribo.zone’s text adventure entry for #Nature
- anhvn’s SVG theme changer entry for #Color
- weird.sha.codes CursedTube for #Cursed
- jzplusplus’s #API entry which just redirects to a random other WWO website
- Pretty much everything put out by Marie Malarme. Incredible!
Big shoutout to weird.sha.codes and Marie Malarme for being, I think, the only people to complete all 31 entries!
General Tech Thoughts
Naturally, I used the most hacky and fast code I could to make my ideas come to live. Like I mentioned above though, that really helped me push past my unhelpful perfectionism! But, uh, don’t use any of this code without checking it first, bugs abound!
I learned a lot making these sites. Despite working in Web tech for ten years, my mental model of CSS still feels bad, and this helped improve it a bit!
I also learned a bunch of new-to-me CSS features (:has
,
:target
) and a few new JS APIs (MediaDevices).
I did my best to make every website work across different viewports, although some, like the keyboard only game for #Dimensions or the phone only experience in #IRL didn’t meet that goal.
I definitely skimped on some accessibility stuff – almost none of my images had alt text, and I’m sure keyboard navigation was broken on some sites. That said, most were so simple that I (hope) they work ok with a screen reader.
Overall, this was a way to flex my programming muscles, learn some new things, and ignite a passion for web dev again.
Now on to a breakdown of my entries :)
My Entries
#10 Art
This was a fun recreation of my favorite Emily Dickinson poem: Because I could not stop for death.
I cheated on this one, because I’d already recorded the song and drawn the pixel art grim reaper animation before I’d learned about WWO.
But! WWO gave me the motivation to fully bring this idea to life. See above about finishing projects…
Tech Notes
Don’t have much to say on this technically, other than it was a PITA to splice together my poem audio with the Devil Went Down To Georgia mp3. I figured it would be easy with built-in MacOS audio tools, but apparently not.
#11 API
This one was much more of a technical challenge! In it I report back all the information that my server gathered when you visit the site, including IP address, a map of where you are, and cookie information.
My intent was to show people the information that a website can collect on you, just by visiting it. Like, do you know that your IP address can be used to find your general real-life location? If your IP can be associated with your name somehow, a malicious web server can report where you are. Spooky!
I’d like to expand this website into a more detailed op-sec post someday… I’ll link it here if I ever do!
Tech Notes
This site actually marks the first path on ragman.net to be served non-statically. My server is written in Go, and I had to make a quick little function to inject the request information into the served HTML.
I spent way too much time trying to get the JSON to be syntax highlighted. Ended up just calling pandoc which is what I use for the code snippets on this blog.
I also used Leaflet JS for the first time, to display the map of the visitor’s location. I was happy with how easy it was to use.
#12 Ritual
I’m quite fond of this one. And now I’ll always have something to show people when they ask me about tea!
It’s fun to make simple projects like this – just sharing something you love with the world.
Tech Notes
I was inspired by ribo.zone’s text adventure entry when I was creating this site.
They use a cool CSS-only technique to display different
<section>
s when the visitor clicks a link.
Each <section>
is the same size, absolutely
positioned, and has a unique id
. Each
<section>
initially has a z-index
of
auto (0), meaning they’re all placed on top of each other, in the order
that they’re laid out in the HTML.
Each <a>
link on the page contains an anchor to
one of the <section>
ids, e.g. https://ribo.zone/weirdweb/nature/#look.
They then use the CSS :target
pseudo-class to
selectively give whichever <section>
is currently
active a higher z-index
of 2, which causes it to be
displayed above the rest.
And v’oila! Javascript-free text adventure!
Unfortunately, this technique only works when each
<section>
is the same size. If the section with
z-index
2 was smaller than a section below it, the other
section would be visible behind it.
I wanted to use images of different sizes, so I had to come up with a way to solve this. Fortunately, with some googling around, I was able to come up with:
section {display: none;
}
:has(~ section:target) {
sectiondisplay: none;
}
:first-child,
section:target {
sectiondisplay: flex;
}
The section:has(~ section:target)
selector is required
to hide the first-child
section when another section is the
current target.
It can be translated into English as:
Select all
<section>
that have a subsequent sibling<section>
which itself has an id that matches the current URL fragment.
I could’ve avoided it if the initial URL always had an anchor
fragment (e.g. https://www.ragman.net/wwo24/ritual/#morning_tea),
but I wanted to make sure the normal /wwo24/ritual/
path
worked.
I’m thinking of redesigning my home-page to be text-adventure-y, and I might use this technique to do so!
One final note is that this is the entry where I used this footer format, which I stole from Mitchell Busby’s #blessed entry.
#13 GIFs
Perfect Loop GIFs! I was briefly into perfect loop gifs in, like, 2015, and had a small collection of them on my computer.
I remembered some blog post I’d read back then talking about a tool that the author had made to automatically pull them out of videos.
I’m not sure if this post was the one I’d read back then, but I found it while Googling, and decided to try to run Python the code provided!
I’m happy with some of the ones I’d found, especially the Porco Rosso one above.
And here’s one I found later on Halloween when I ran Scooby Doo and the Witch’s Ghost through it. It doesn’t hold up nearly as much as the Zombie Island one, but it was a fun movie to watch on the spookiest day of the year! :D
Tech Notes
It took me some tweaking to get the coding working, since the moviepy library had changed a bit, but I eventually got some code running! Unfortunately, I updated pip on my machine sometime after doing this entry, and now my code is broken, and I don’t want to go back and fix it. Maybe one day!
I’m trying to get into the habit of downloading youtube videos (using yt-dlp) that I want to watch, rather than watching them on YouTube itself, where I inevitably get sucked into watching more and more videos.
This meant that I had a collection of videos already on my computer, which I fed into my program. It took ages to run, and the algorithm wasn’t that precise, so there were a bunch of false positives.
I’d like to improve the algorithm, or see if somebody has better code out there, because I really like these gifs!
#14 History
I had originally wanted this history to be about, ya know, actual history. I had just finished listening to the Cool People Who Did Cool Stuff episode about the “Kabouters in late 60s Amsterdam who rebuilt the city”.
I think I wanted to do something with their planting of the orange tree by the Amsterdam National Monument. But I decided I didn’t have a clear idea, so I pivoted, and decided to focus on browser history.
Give the website a try, and you’ll see that if you click the links you’ll eventually get multiple browser entries that together read: “Remember to drink responsibly”.
I think the browser history hacking was a cool idea, but not one that I executed very well. I had multiple friends not actaully check their browser history, and having the final link still look like a link was confusing. Things to improve on!
Tech Notes
I figured the only way that a website has to interact with a browser’s history is through the document title. I did some testing to see if it updated if you changed the document title with javascript, and lo and behold it did!
I used URL fragments to provide a unique entry in browser history for each clicking of the link, and then changed the document title using Javascript.
You can see the changed title in a desktop browser tab, though not really on mobile.
Most people have so many tabs open on their browser though that it won’t give it away :P
#17 Dimensions
This is maybe the entry I’m most proud of. If you haven’t played it yet, please do, it only takes about ten seconds :D
Somehow, even though I know the joke is coming, it still cracks me up everytime.
I’d originally thought of doing some homage to Flatland by Edwin Abbot, but I gave it a skim, and boy sexist and classicst than I remembered. I mean, that’s the entire point of the book, to criticize the society at the time, but it wasn’t what I was in the mood to read.
Fortunately, this Mario idea popped in my head! We have Super Mario 3D World – why not 1D world?
Some sprite assets, music, and hacky javascript code later and the game was born!
Tech Notes
Don’t have too much to say about this one. I messed around with
sprite importing, which was fun, and wrote some really hacky
canvas
code to animate them.
Feel free to look at the code, but I wouldn’t recommend copying anything from it!
One effect I really liked was Mario getting stuck to the Goomba and being pushed off. In the normal Mario games, he would fall off the bottom of the screen when he dies, but this is 1D baby!
#18 Words
A riff on the expression “A picture is worth a thousand words”. Cuz like, sure, you need a thousand words to describe a painting.
But you can evoke a whole image with just a few words:
Disco ball
Or even a whole emotional response:
Your first crush
I really like how this one turned out, though it didn’t get the reception I wanted from my friends 😅.
Tech Notes
This one was pretty simple, the only tricky thing was swapping out the word in place. The correct amount of padding had to be added to both sides to make it centered.
function getPaintingHTML(painting) {
// const frameLength = paintingEl.innerText.length
// In other words, "P I C T U R E F R A M E P I C T U R E".length;
const numPadding = (frameLength - 4 - painting.length) / 2;
const paintingLink = `<a id="painting_link" href="javascript;">${painting}</a>`;
return `R ${' '.repeat(Math.floor(numPadding))}${paintingLink}${' '.repeat(Math.ceil(numPadding))} E`;
}
#21 Memes
Meme music! Millenials’ contribution to the annals of history!
This one was a lot of fun. I ended up going with five meme songs:
If you’re reading this and want me to add one, let me know :P
I mostly showed these sites to friends or family that are my age and older, so not all of these landed. Skibidi Toilet was off-putting to the older generations, and I was surprised at how few people knew the Leekspin meme.
The headbanging cat was a universal hit though.
Tech Notes
This one was pretty straightforward, but I did run into an issue with Skibidi Toilet audio synchronization.
It was the only meme that had a strict start point, in contrast with, say, the endlessly animating Nyan Cat, so its music had to be synced up.
The code refetches the gif when you transition to Skibidi, but if you click the “Turn on music” button in the middle of the gif, it’ll be out of sync.
I ran into another problem with the gif – it was so large that it wouldn’t be loaded by the time the music had started. In fact, I was originally hitting server write timeouts, which were causing the GETs to 503.
I upped the timeout, which fixed that, but on slower internet connections the download was simply too laggy to sync up.
One way to fix this might be to wait until the image is completely loaded before playing the audio… but hey, it was made in a day! :P
Ideally I would also use a <video>
tag instead of
gifs, which would solve the audio sync issues, and be much more
bandwidth friendly. But I couldn’t figure out how to get the
background-repeat
effect with a video tag, so I gave
up.
#22 Bugs
I don’t really have anything else to say about this one that isn’t said in the post: Go read it!
Only thing is that the background picture is of the Wissahickon Park in Philadelphia, one of the largest urban parks in the country. If you’re ever in the area, I recommend visiting!
#22 IRL
To my eternal shame, I said called the character a digital crocodile when he’s really an alligator. I even got him in New Orleans! 🙈
I had a good time with this one. Made me think about how (relatively) easy it is to create a little interactive story. I want to do more things like this in the future.
I recorded the audio in my bedroom, took a photo of the Digital
Crocodile Alligator, and then used Aseprite to remove the
background (I really should learn how to use Photoshop one day).
When sharing this with friends, many didn’t read the message to turn their audio on, and so didn’t understand what the point was. In the future, I’d make that the only warning message, to hopefully get people to actually read it!
Tech Notes
This was my first time using the MediaDevices API, to get the camera stream.
It turned out to be very easy! All the code to get the stream and put
it in a <video>
tag, and play the audio once it had
loaded is below:
const constraints = {
video: {
facingMode: {
exact: 'environment'
}
};
}
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
.srcObject = stream;
videoElcatch (e) {
} .style.display = 'none';
videoEl.innerHTML = "<p>Could not get rear-facing video stream.</p><p>If on a phone, check permissions, refresh, and try again.</p>";
splashEl.style.display = 'flex';
splashEl
}
.addEventListener('loadeddata', () => {
videoEl.addEventListener('playing', () => {
audioEl.style.opacity = 1;
crocEl;
}).addEventListener('ended', () => {
audioEl.style.opacity = 0;
crocEl;
})setTimeout(() => audioEl.play(), 1000);
, { once: true }); }
Great job to the API developers!
I did have a little bit of trouble getting it to work on IOS – it was popping the video into some full screen thing, which then made it so the crocodiile didn’t show.
The fix was adding the following attributes to the video tag:
<video autoplay playsinline></video>
Thanks to A for the help debugging!
#30 Emoji
I took a week long break before after the IRL entry, due to sickness and travel. Came back strong with this one though!
The emoji translator was inspired by my friend and former co-worker G$, who would write like this all the time.
I sent this website to him, and he said that he’d been thinking of making something similar for a while – I’m happy it met his approval :D
I had always planned on adding the Smash Mouth example, but I added the Hobbit example because I wanted something more “literary”. Then I added the Monkey King snippet because I was reading it at the time. Definitely took some liberties with “North”, “South”, etc.
Tech Notes
The translator is pretty simple, it just takes every word of text and looks for the largest substring inside that corresponds to an emoji.
For example, the word “told” gets turned into “t-👴” (t-old). Looking for the largest substring is important, because otherwise short emojis would be everywhere, and longer ones would never be shown, e.g. “told” would be “2️⃣ld”.
The key to the translation is the word -> emoji mapping, which
currently is something 623 entries. I created it by scraping https://unicode.org/emoji/charts/full-emoji-list.html
and filtering for any emoji whose CLDR Short Name
was only
one word long.
So it got robot -> 🤖
, but doesn’t have an entry for
alien -> 👾
because the short name is actually “alien
monster”.
If I’d had more time I would’ve gone through and added a bunch more emojis from the longer CLDR names, which would make the translation much more impressive.
When I shared this with a friend, he said that somebody at his work had made a Slack bot that did this, but using Levenshtein distance to convert every word to the emoji it was closest to.
I gave this a try, and found the results pretty incomprehensible. Maybe it would work for short strings of text, but meaning isn’t preserved across multiple sentences.
Right now it only translates one way – it would be fairly simple to go the other direction, so that’ll be future work for sure.
I might also make a browser extension that does this, just for funsies 😄.
And that’s a wrap! I had a blast making these sites. It really sparked my creativity – I want to find a way to keep doing similar throughout the year.
If you read through all this, thank you! You rock! And I hope you go out there and make something creative yourself! ❤️