MoreRSS

site iconJim NielsenModify

Designer. Engineer. Writer.20+ years at the intersection of design & code on the web.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Jim Nielsen

Making o(m)g:image, Part IV: URLs

2024-12-24 03:00:00

This is part four of my series of posts describing how I made my quiz game o(m)g:image.

The design of the game is simple:

  • Each page has one question with four possible answers
  • When an answer is chosen, show users if they were right or wrong
  • Provide the ability to go to the next question

My first thought is to make each question a <form> with some <input> radios, e.g.

<img src="question-1.jpg">
<form action="/questions/1/">
  <label>
    <input type="radio" name="answer" value="1">
    First answer
  </label>
  <label>
    <input type="radio" name="answer" value="2">
    Second answer
  </label>
  <!-- the other 2 questions -->
</form>

That makes a lot of sense for a quiz, but I realize that it necessitates a URL structure like this:

  • Question: /questions/1
  • Answer: /questions/1?answer=1

Why search params? Because that’s how a native HTML <form> submission works by default.

But I don’t want that because it requires either 1) something other than a static file server, or 2) client-side JavaScript.

With this project, I know what I don’t want:

  • I don’t want to manage a server
    • e.g. something to dynamically process a request to /questions/1?answer=1 and respond with the appropriate HTML
  • I don’t want to use some special, host-specific mechanism for routing
    • e.g. a _redirects file to re-route /questions/1?answer=1 to the file /questions/1/answers/1.html on the server
  • I don’t want to write any JavaScript for form submissions
    • e.g. to take a form submission that would normally route to /questions/1?answer=1 and re-route it to /questions/1/answers/1

So what do I do? I use the same technology used on the SpaceJam website from 1996 to present a quiz: links!

<img src="question-1.jpg">
<a href="questions/1/answers/1">First answer</a>
<a href="questions/1/answers/2">Second answer</a>
<!-- other 2 answers -->

This allows me to have a URL structure like this:

  • Question: /questions/1
  • Answer: /questions/1/answers/1

Which allows for:

  • A static file server
  • No special routing logic
  • No JavaScript

Form submissions as well as links can be navigations.

It’s boring — almost feels unworthy of a blog post — but it’s solid approach to making a website that will require very little intervention in the coming years/decades. Hopefully this quiz lasts as long as the SpaceJam one from 1996.


Reply via: Email :: Mastodon :: Bluesky

Tagged in: #omgImg

Related posts linking here: (2024) Making o(m)g:image, Part I: Design Iterations ::(2024) Making o(m)g:image, Part II: As Little JS As Possible ::(2024) Making o(m)g:image, Part III: The HTML

Making o(m)g:image, Part III: The HTML

2024-12-19 03:00:00

This is part three of my series of posts describing how I made my quiz game o(m)g:image.


o(m)g:image is presented like a quiz:

  • You get one question at a time
  • When you choose an answer, it shows you if you got it right (and, if you didn’t, what the right answer is)
  • You go to the next question

To do this, I generate an HTML page for every question and for every possible question’s answer (correct or incorrect).

In my source code I have a file that outlines all the questions and their answers, e.g.

- id: 1
  answerId: 2
  answersById:
    1: The Ultimate Guide to Developer Happiness
    2: The Key to Building High-Performing Teams
    3: Woman Wins $500,000 at Georgia/Alabama Football Game
    4: A Guide to Sizing Wedding Rings
- id: 2
  # more data here

I use Web Origami as my build tool to loop over all this data and output an HTML page for 1) each question, and 2) each answer for each question. The URLs for accessing these pages follow this pattern:

/questions/:question-id/answers/:answer-id

So, for example, question 1 has 4 possible answers and answer number 2 (in the source data) is the correct one. So for that question, I create an HTML page for:

  • /questions/1
    • e.g. “Here’s the question and its possible answers”
  • /questions/1/answers/1, /questions/1/answers/3, and /questions/1/answers/4
    • e.g. “Sorry, you got it wrong. Answer 2 was right.”
  • /questions/1/answers/2
    • e.g. “You got it right!”

Because the game is structured this way, I have a pretty good idea how people will flow through the game:

  1. Question 1, then
  2. Question 1: answer 1, 2, 3, or 4, then
  3. Question 2, then
  4. Question 2: answer 1, 2, 3, or 4, then
  5. Etc.

So when a user lands on a question page, e.g.

/questions/1

And they click on an answer, rather than go fetch that page with JavaScript and insert it into the current page, I go ahead and let the browser’s default take over which is to do a full page reload.

However, I optimize for this by preloading the HTML pages where the user will go next. So if they’re on /questions/1, I preload each answer page for that question:

<!-- /questions/1 -->
<link rel="preload" href="/questions/1/answers/1/" as="fetch">
<link rel="preload" href="/questions/1/answers/2/" as="fetch">
<link rel="preload" href="/questions/1/answers/3/" as="fetch">
<link rel="preload" href="/questions/1/answers/4/" as="fetch">

Then, when they click on an answer, the browser already has that HTML page. It loads fast! For example, here’s a question page and you can see in the Network tab of the Developer Tools how it preloaded each answer’s HTML page.

Similarly, when someone lands on an answer page for a question — like /questions/1/answers/2 — I have a pretty good idea where they’ll go next (the next question!) so I preload that:

<!-- /questions/1/answers/2 -->
<link rel="preload" href="/questions/2/" as="fetch">

Once again, things load fast!

This is possible, in part, because every HTML page is small. Here’s a screenshot of “View source” on the first question:

Because these pages are so small in size, it’s totally negligible to just load four or five of them in anticipation of a click.

Side note: I considered inlining all my CSS as a <style> tag for each HTML page. But then as I thought about how I wanted to preload answers, I realized using a <link> to a single CSS stylesheet across all pages would be even better, because while it’s one extra request it gets cached for every subsequent HTML page — trade-offs! That’s, in part, how every HTML page can be so small.

What I love about all of this is that I don’t have to write any JavaScript. Because the UI is so small, and I designed it so each element on screen doesn’t shift in layout from one page to the next, it can feel like a SPA with in-place transitions but it’s not! It requires zero JavaScript to achieve.

It feels akin to those tricks they use in film, like forced perspective, where you can achieve the illusion of something without the cost of special effects (very useful on small budgets). For example, I love this explanation of how they made the actors for Gandalf and Frodo in The Lord of the Rings appear in the same scene and, even though they’re both full-sized humans, one appeared much more hobbit-sized purely via some tricks of cameras, tracks, and props — zero computerized special effects required!

It feels a bit analogous to web development, where personal-sized projects don’t want to pull out the big “special effects” JavaScript used by big studios. You can achieve a similar, perhaps even better, effect using a few little tricks.


Reply via: Email :: Mastodon :: Bluesky

Tagged in: #omgImg

Related posts linking here: (2024) Making o(m)g:image, Part IV: URLs

Making o(m)g:image, Part II: As Little JS As Possible

2024-12-16 03:00:00

This is part two of my series of posts describing how I made my quiz game o(m)g:image.


One of my goals when making this project was to use as little JavaScript as possible.

In retrospect, I have to admit that was a pretty ambitious goal. Not because it was hard from a technical point of view, but because it was hard from the point of view of human nature.

Allow me to explain.

I write a lot of JS for my day job. And I write a lot of JS for other small projects (not necessarily websites, but scripting data, tools, etc.) And when you have the great and might hammer of JS, every solution looks like a nail.

So it required a lot of self-discipline to avoid mindlessly falling into the practice of throwing JS at every problem I wanted to solve.

For example, I could’ve made this whole site as a “single page react app”. But I’ve done that on prior personal projects and now I don’t want to ever touch any of those projects (I also hope I won’t have to). I mean, we’re talking projects with React before its major version jump, like React 0.14.x with webpack/babel/etc. (Oh young Jim, you didn’t understand as well back then.) But I’m a little smarter now, so my goal on this project was: as little JS as possible.

But JavaScript is like a smartphone: as much as you don’t want to use it, you can’t help yourself. Again, it required a constant, mindful willingness to keep stepping back and asking myself, “Can I do this without JavaScript — if possible?”

In the end, I like where I landed. Every “question” is an HTML page. Every “answer” is an HTML page. Even the “navigation” where you can see how all the questions, is an HTML page.

For example, at one point I wanted to have some navigation on each page. My first thought was something interactive on the page, like a widget that says “Question 4 of 12”, you click on it, then it expands and shows you all the questions on the quiz, including which ones you’ve answered and whether you got them right or wrong.

How do you do that without using JavaScript?

At first I thought, “Use a <details> element, it won’t require JavaScript!” But as I thought through the ramifications, I wasn’t in love with the solution.

The more I thought about it, the more I wanted to reach for JavaScript: a fancy slide out drawer, a slick popup menu, an in-place expand. So! Many! Options!

But I wanted to avoid JS as much as possible — ”only for what’s absolutely critical” I told myself. Could I show/hide navigation with only HTML?

What I landed on was navigation as an HTML page. Every page has a little piece of information that tells you what question you’re looking at, e.g. “Question 1 of 12”.

Screenshot of o(m)g:image with an image, 4 possible answers, and a title at the top that says “Guess the article” and a subtitle that says “Question 1 of 12” with a little down-pointing arrow next to it.

If you click on it, rather than expanding the navigation inline, or popping out a drawer with the navigation, or doing something else that requires JavaScript to reveal the information, it simply takes you to a new HTML page with the information. (The browser’s back button serves as the equivalent of a “close drawer” or “collapse this” JavaScript solution.)

An interface displaying a grid of 12 numbered square buttons, arranged in three rows and four columns. Above the grid, the word ‘Questions’ is displayed in bold with a subtitle indicating ‘12 total.’

The only enhancement to the game that actually uses JS is the part that keeps track of which questions have been answered and whether the answer was correct or not. If you have JavaScript enabled, you will see this information additionally layered onto this navigational page’s UI.

An interface displaying a grid of 12 numbered square buttons, arranged in three rows and four columns. Above the grid, the word ‘Questions’ is displayed in bold with a subtitle indicating ‘12 total.’ Questions 1, 2, and 3 are in red with a little “x” in the bottom right corner of the box indicating they were answered incorrectly. Question 4 is green with a checkmark, indicating it was answered correctly.

With this approach, I’m truly using JavaScript for what JavaScript alone can do which is keep track of client state (you might argue I could have a server with a session here, but another constraint of the project was no server, so yeah).

This HTML-first approach really drove the design of the site. I had to ensure that navigational elements always stayed at the top of the page in consistent places across pages to ensure the UI didn’t jump as you navigated around (from the top of the page). My goal was to make the site so lean and so fast, that multi-page navigations felt as close as possible to in-place, on-page interactions.

I’m pretty happy with where the project ended up technically. I think it holds a great tension of low-cost maintenance for me over time, but still a great, solid experience for end users across a wide variety of devices — and, hopefully, across a long period of time.

The whole exercised really impressed on me once again that sometimes we just need to build simpler UIs. If your frontend is hard to build, it’s probably hard to use. If you make it easier and simpler to use, it’ll likely be easier and simpler to build and maintain.

That said, the irony here is that it’s actually quite hard to build something like this because you have to make ruthless choices about cutting things that aren’t absolutely necessary.

In other words, building websites backed by MBs of JavaScript is hard. You know what’s even harder? Questioning whether you actually need to build what you’re building, and cutting it down to the essentials. (HTML is really good at essentials.)

It reminds me of that old saying about how shorter writing requires more time, except for websites, i.e. “My website is lots of JavaScript. If I’d had more time, I would’ve made it lots of HTML.”


Reply via: Email :: Mastodon :: Bluesky

Tagged in: #omgImg

Related posts linking here: (2024) Making o(m)g:image, Part I: Design Iterations ::(2024) Making o(m)g:image, Part IV: URLs ::(2024) Making o(m)g:image, Part III: The HTML

Making o(m)g:image, Part I: Design Iterations

2024-12-13 03:00:00

This is part one of my series of posts describing how I made my quiz game o(m)g:image.


I blogged about my recent project omgimg.jim-nielsen.com and I figured I’d write more details about my process behind making it.

When the idea first struck, I jumped into Figma and started working out the idea. I had a pretty good idea of what I wanted: a quiz-like website that showed one question per page.

Quiz interface asking ‘Can you guess the article?’ with an image of a football game scene and four multiple-choice options below, including humorous and unrelated article titles. On the right, a logo for ‘omg:image’ with the text ‘Every article in the world doesn’t need an image to summarize it. Hot take.’

Once you answered it gave you the result and prompted you to continue to the next question. Pretty basic stuff. In no time, I had something in Figma that fit the bill.

Quiz interface with the message ‘Sorry, but that guess was incorrect!’ displayed in red. The image shows a football game scene, and below it, the correct answer, ‘The Ultimate Guide to Developer Happiness,’ is highlighted in green. An incorrect guess, ‘A Guide to Sizing Wedding Rings,’ is marked in red. A ‘Next’ button is visible at the bottom. On the right, the logo ‘omg:image’ appears with the text ‘Every article in the world doesn’t need an image to summarize it. Hot take.’

From there I was anxious to jump into the code, so I built the Figma mockup in code. (I built it using Web Origami, more technical details later.)

I am hosting the site on Netlify, which has atomic builds for each commit. That means I am lucky enough to go back through my deploy history and view the site at different commit iterations, so I nabbed some screenshots for this post.

The first version was pretty close to the Figma mock.

Side note: Figma, for me, is a tool for settling on the direction of a website’s design and structure, not a “implement this screenshot pixel-for-pixel” tool, so I knew things were going to change and evolve as I built the site in the browser. Building the site is part of the design process.

Screenshot of iteration number one for o(m)g:image, showing how the layout changed somewhat from the original Figma mock as the surface the quiz lives on, for example, does not extend to the full heiht of the browser window like it did in the Figma mock.

That worked, but I didn’t love it. The relationships between elements didn’t quite feel right, so I started playing with layout and other small things.

Screenshot of iteration number one for o(m)g:image, showing how the layout changed. For example, the entire question became one big visual chunk, isntead of discrete UI pieces.

From there, I started clicking around and going through each question. This gave me a feel for the experience of answering questions, seeing the result, and moving on to the next question.

I had figured out (in my mind’s eye) how this would work in Figma, but only once I had a working prototyping in a web browser could I really get a feel for how the Figma design held up. This gave me a chance to say to myself, “Oh well that doesn’t work, and I’ll have to change this, and this thing over here...”

That's how I ended up on another iteration where I tweaked element positioning to try and better the experience of: seeing a question, answering it, then viewing the result.

Screenshot of iteration number three for o(m)g:image. More elements have been moved around, including the “Guess the article” title is now left-aligned with the question count to the right. That way, when the page changes, they reveal new information but stay in the same spot.

Screenshot of iteration number three for o(m)g:image for the answer page, where the incorrect answer is crossed out and the title “Guess the article” has been replaced with the text “Incorrect”.

After I got the navigational elements to a place that felt better (and indicating your progress through the quiz), I went back to layout and played with the overall positioning of elements. I also continued to refine and tweak the elements that helped you navigate through the questions of the game (and their corresponding answer pages).

Screenshot of iteration number four for o(m)g:image where the article heading has changed to be laid out top-to-bottom, heading then quesion status.

Screenshot of iteration number four for o(m)g:image answer page where the article heading has changed to be laid out top-to-bottom, heading then quesion status. On the left is a control to restart the question. On the right a control to go to the next one.

At this point I ended up taking a detour on the navigation front.

I kept waffling between the idea of minimal representation of the number of questions in the game (e.g. “1/10 questions”) and something more robust that would let you jump to any question at any point. So I built a version to test out that idea.

Screenshot of iteration number five for o(m)g:image where there’s a numer for every question in the quiz that you can click on.

creenshot of iteration number five for o(m)g:image answer page where there’s a numer for every question in the quiz that you can click on and the currently active answer page is red because the question was answered incorrectly.

Then I ended up on a side quest of multi-page view transitions, because it was cool to be able to design the navigation in such a way that I could have multiple HTML pages that transitioned visually when navigating between them.

Animated gif showing numbers 1 through 8, the active one with a circle around it and as each one is clicked and becomes active the circle eases over to that position in an animation.

But I ended up deciding I didn’t want to go in that direction, so I went back to a minimal representation of question status/progress. I also changed the layout (yet again). These were all decisions I made after repeated usage of the game, just clicking through things over and over and over until I found an experience that felt seamless and fast.

Final version of the question page. Quiz interface titled ‘Guess the article,’ labeled as ‘Question 1 of 12.’ It features an image of a football game scene at the top, with four multiple-choice options below, such as ‘The Ultimate Guide to Developer Happiness’ and ‘Woman Wins $500,000 at Georgia/Alabama Football Game.’ At the bottom, the text ‘o(mg):image - Can you identify these articles from the web by their social share images?’ appears, with a credit link to ‘Made by Jim Nielsen.’

Final version of the answer page. Quiz interface with the title ‘Incorrect’ and the label ‘Question 1 of 12.’ The image of a football game scene is displayed at the top, and below it, the correct answer, ‘The Key to Building High-Performing Teams,’ is highlighted in green, while the incorrect guess, ‘The Ultimate Guide to Developer Happiness,’ is crossed out in red. The other two options remain unselected. A forward arrow button is visible on the right. At the bottom, the text ‘o(mg):image - Can you identify these articles from the web by their social share images?’ appears, with a credit link to ‘Made by Jim Nielsen.’

I also decided that, rather than have all questions laid out in the navigation for every single question, I would have a single page with all the questions represented on it. As an enhancement, people with JavaScript would see their progress on that page including the correctness of their answer for each question.

Quiz results page titled ‘Questions’ with a grid showing the status of 12 questions. Correct answers are marked with green checkmarks (e.g., questions 2 and 5), and incorrect answers are marked with red Xs (e.g., questions 1, 3, and 4). The remaining questions are unattempted. A ‘Reset results’ button is centered below the grid. At the bottom, the text ‘o(mg):image - Can you identify these articles from the web by their social share images?’ appears, with a credit link to ‘Made by Jim Nielsen.’

That felt good to me, so I shipped it and that’s the state of the site at the time of this writing.

It’ll probably change in the future because every time I use it I think, “Oh I could tweak this, and move that...”

That’s the curse of building these things yourself.


Reply via: Email :: Mastodon :: Bluesky

Tagged in: #omgImg

Related posts linking here: (2024) Making o(m)g:image, Part IV: URLs ::(2024) Making o(m)g:image, Part II: As Little JS As Possible ::(2024) Making o(m)g:image, Part III: The HTML

Introducing o(m)g:image

2024-12-10 03:00:00

I was popping off on Mastodon with an idea for a physical board game then decided to just make a digital version. It’s called “o(m)g:image” and you can play it now:

omgimg.jim-nielsen.com

Here’s the idea:

  • You have a bunch images
  • Each image is a real-life og:image pulled from an online article
  • You try to guess the title of the article based solely off the social share image

For example, can you guess the title of the article for this image?

Screenshot of a shared link preview user interface, where the image depicts a woman, wearing a white Dr Pepper jersey with the number 23, appears emotional and ecstatic as she stands on a football field during a competition. In front of her, there are footballs and large containers used for a throwing challenge. A cameraman captures her reaction, while cheerleaders and a crowd of spectators in the background cheer her on. The atmosphere is lively and celebratory, suggesting she may have won or completed a successful challenge.

Is it:

  1. The ultimate guide to developer happiness
  2. Florida woman wins $500,000 at Georgia/Alabama football game
  3. A guide to sizing wedding rings
  4. The key to building high-performing teams

Answer: it’s an article from slack.com and its real title is number 4 above.

Did you get it right?

Without question, we seem to have accepted (and stretch ourselves to support) this idea that every web page in the entire world should should be annotated with an image that encapsulates its contents into a single, visual expression.

This game is meant to illustrate the absurdity of this notion in both principle and real-world execution.

Why Make The Game?

I often vent about social share imagery to anyone who will listen — both in person and on my blog.

Sometimes a satirical game can convey the idea better than words.

What’s Wrong With Social Share Images?

Nothing inherently.

But because they’re possible, everyone feels like they’re leaving clicks and pageviews on the table if they don’t implement them.

“You’re not going to have an image for every post? Are you a crazy person?! People are 1,267% more likely to click through to your website if you have one!”

So everyone makes them, and as a result they end up stretching themselves — and the meaning between the image the content — to come up with these images.

That article has a picture of a clock? It could be about anything, such as:

  • Time management & productivity
  • History of clocks and time keeping
  • Time dilation and physics
  • Aging and mortality
  • Technological advancements in timekeeping
  • Time travel and fiction
  • Biological clocks

It could also have nothing to do with clocks and just be an article for a technical blog where nobody could come up with a better idea of how to visually express “Latest news from our release of [insert codebase name here]”.

For anybody who has done it, the task of coming up with a good image for every article is a tough one. The web has turned everyone into a publisher, but not everyone is backed by a large media corporation that employs people to do this task as their full-time job. If you as an individual or a small group of people try to compete with the quality of social share imagery from media publications, it’ll never be as good. Full stop. In other words: you’ll never be as good at the game as the companies who do it for a living, so the only way to win is not to play the game.

Or, you can try to compete and then you reach for automation. As Nicholas Carr says, “The endless labor of self-expression cries out for the efficiency of automation.” But automated social share images have their downfalls:

  • They’re bland and generic (e.g. stock photography or AI-generated imagery)
  • They’re unrelated to the content (e.g. abstract computer-generated patterns and shapes)
  • They’re duplicative (e.g. an image with text for the title and description of the article, which is already included in og:title and og:description and, on many social sites, displayed under the image itself)

So What Are They Good For?

Social share images are basically billboards on the information superhighway. Perhaps they were intended to convey additional context about the article, but in practice they’re really just screaming to catch your eye (and attention) and hopefully make you click.

Perhaps even better than a contextual image summarizing the content of the article, these images should just have some flamboyant visual with giant, bold type set in Impact that says, “BET YOU COULD NEVER GUESS WHAT IS HERE! CLICK TO FIND OUT!!!"

Look I get it. Social platforms are giving you the chance to have a free billboard, one that might grab the attention of all the people passing it by in their feeds. Why wouldn’t you leverage a free billboard?

I mean this game, which makes fun of social share images, even has one. So who the hell am I to say anything?

Nobody really, just another person on the internet.

Again, you can check out the game at: omgimg.jim-nielsen.com


Reply via: Email :: Mastodon :: Bluesky

Tagged in: #omgImg

Related posts linking here: (2024) Making o(m)g:image, Part I: Design Iterations ::(2024) Making o(m)g:image, Part IV: URLs ::(2024) Making o(m)g:image, Part II: As Little JS As Possible ::(2024) Making o(m)g:image, Part III: The HTML

Contrast Is Clarifying

2024-12-03 03:00:00

Which is best?

  • Generalist or specialist?
  • Native or web?
  • Web site or web app?
  • JavaScript or Typescript?
  • Framework or library?
  • Server side or client side?
  • Photoshop or Sketch or Figma?
  • Designing in a tool or design in the browser?
  • Skueomorphic or flat?
  • Mac or PC or Linux?

This list could go on forever. Zoom in to just the JavaScript ecosystem and its overwhelming:

  • Modules: ESM, CJS, AMD, UMD
  • Package managers: npm, yarn, pnpm, bower
  • Bundlers: Webpack, Rollup, Parcel, Bun, Vite
  • Compilers: Babel, TypeScript, esbuild, swc
  • Runtimes: Node, Deno, Bun
  • UI frameworks: React, Vue, Angular, Svelte, Lit

Even here, the list could go on forever: db libraries, task runners, testing libraries, UI metaframeworks, server frameworks, state management libraries, etc.

Module systems — ESM, CJS, AMD, UMD — are a great example: how could we truly understand any of them individually without having had all of them?

We need opposing options. They exist not solely in opposition to one another but as contrast.

We must have a diversity to understand and discuss which is most fit for a given context. The web is big! “It depends”!

How do you understand one thing without the contrast of its opposite? What is white without black? How do you understand salty without sweet? One, by definition, excludes the other, which gives form and shape to the definition of each.

And guess what? Whichever you choose, you’ll likely choose poorly. That’s ok. Choosing poorly is where growth happens — if you let it.

Silver bullets are for killing werewolves not building technology.


Reply via: Email :: Mastodon :: Bluesky