<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>soupDilemma – blog blog blog</title>
        <link>https://soupdilemma.neocities.org/</link>
        <description>blog blog blog</description>
        
        <item>
            <title>Choice-based stories using CSS</title>
            <description>&lt;p&gt;In this post, I’ll go over how to make a basic &lt;a href=&quot;https://www.ifwiki.org/Choice-based_interactive_fiction&quot; target=&quot;_blank&quot;&gt;choice-based story&lt;/a&gt; on a single web page without using any JavaScript or server-side logic. Some knowledge of HTML, CSS, and basic file operations is assumed.&lt;/p&gt;

&lt;p&gt;Thanks to Dij for beta reading this post!&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#background&quot; id=&quot;markdown-toc-background&quot;&gt;Background&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-story-file&quot; id=&quot;markdown-toc-the-story-file&quot;&gt;The Story File&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#making-it-interactive&quot; id=&quot;markdown-toc-making-it-interactive&quot;&gt;Making It Interactive&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hiding-information&quot; id=&quot;markdown-toc-hiding-information&quot;&gt;Hiding Information&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#footnotes&quot; id=&quot;markdown-toc-footnotes&quot;&gt;Footnotes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;I’ve been playing &lt;a href=&quot;https://grundos.cafe/&quot; target=&quot;_blank&quot;&gt;Grundo’s Cafe&lt;/a&gt; for a little bit, having largely given up on Neopets due to its aggressive monetization. One of the things I remember wanting to do as a kid was finish making something with the Neopian Adventure Generator, which is nothing special if you’re at all familiar with &lt;a href=&quot;https://twinery.org/&quot; target=&quot;_blank&quot;&gt;Twine&lt;/a&gt; or pretty much any other tool for choice-based interactive fiction – the only state it tracks is what passage the player is on. All the same, it had its charm.&lt;/p&gt;

&lt;p&gt;Grundo’s Cafe doesn’t have the Neopian Adventure Generator, but what it does have are pet pages.&lt;sup id=&quot;fnref:pet&quot;&gt;&lt;a href=&quot;#fn:pet&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; Pet pages don’t allow JavaScript, and only a limited subset of HTML and CSS. It might be tempting to look at this set of limitations and say there’s no way to make an adventure using a pet page, but would it surprise you if I said that, even with these limitations, we have just as much power as the Neopian Adventure Generator?&lt;/p&gt;

&lt;h2 id=&quot;the-story-file&quot;&gt;The Story File&lt;/h2&gt;

&lt;p&gt;First allow me to present the HTML file this post will work with, which will not need to change for the rest of the post.&lt;/p&gt;

&lt;p&gt;Save the following in a file with any name that ends in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.html&lt;/code&gt;. How about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void-of-doors.html&lt;/code&gt;?&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;The Void of Doors&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;style.css&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You awaken in an infinite void. Around you are three doors, &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#red&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;red&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; and &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#green&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;green&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; and &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#blue&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;blue&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;red&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;The red door was made of &lt;span class=&quot;nt&quot;&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;FIRE&lt;span class=&quot;nt&quot;&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;! AAAAAAAAAA!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You have been incinerated. &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Try again?&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;green&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;The green door was a reverse green screen! It looked blank but was actually a monster!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You have been eaten. &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Try again?&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;blue&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;The blue door was open air! You&apos;re falling! Which one of these was the parachute pullcord again?! Was it &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#this&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;this one&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; or &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#that&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;that one&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;?!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;this&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;As you pull the cord, your parachute deploys! You drift gently to the ground. Oh, it&apos;s your doorstep!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You have made it home. :)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;that&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;As you pull the cord, your pocket lawnmower revs up! Why do you carry that again?! You accidentally sever your parachute&apos;s ripcord!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You have splatted. &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Try again?&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s obvious I’ve mustered my great skills as a writer to have brought this diversion to life. But how does it play? Try opening the file in a browser.&lt;/p&gt;

&lt;p&gt;… Well, it doesn’t play like much of anything! How are we going to turn this into something more like a game?&lt;/p&gt;

&lt;h2 id=&quot;making-it-interactive&quot;&gt;Making It Interactive&lt;/h2&gt;

&lt;p&gt;You may have noticed there are links but they don’t do anything. Or maybe you noticed they &lt;em&gt;do&lt;/em&gt; do something – they change the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/URI/Fragment&quot; target=&quot;_blank&quot;&gt;URL hash&lt;/a&gt;, which causes the browser to try to scroll to the appropriate passage. But at least on desktop, it’s very unlikely to be able to do &lt;em&gt;any&lt;/em&gt; scrolling because of how short it is, and even if you zoom way in it may not be possible to distinguish the passages at the bottom from each other.&lt;/p&gt;

&lt;p&gt;So the first step is to make the active passage stand out somehow. This can be done with the css &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:target&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:target&lt;/code&gt; selector&lt;/a&gt;! If we make a rule using it, it will cause the element pointed at by the URL hash to change. Make a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;style.css&lt;/code&gt; in the same folder as the HTML file, with the following contents:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:target&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, refresh the page. You should find that clicking the links does &lt;em&gt;something&lt;/em&gt;, namely, it makes one of the passages bigger! Well, it makes &lt;em&gt;most&lt;/em&gt; of the passages bigger, but the first one doesn’t get this treatment. Why’s that?&lt;/p&gt;

&lt;p&gt;We didn’t give it an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;, because it’s supposed to appear even if there’s no URL hash. There’s no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; that corresponds to an empty hash, so this one seems to require special treatment.&lt;/p&gt;

&lt;p&gt;When should the initial passage stand out? Well, when no other passage does. But how do we tell what the initial passage even is? There are &lt;em&gt;many&lt;/em&gt; elements without an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;It looks like we have to pick an arbitrary rule, so how about this: All the elements directly inside the body are passages, and so if an element is in the body and has no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;, then it’s the initial passage.&lt;sup id=&quot;fnref:alternative&quot;&gt;&lt;a href=&quot;#fn:alternative&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Now that that’s decided, let’s try replacing the CSS with this:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:not&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:has&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:not&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Suddenly there’s a much more complicated selector (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body:not(:has(&amp;gt; :target)) &amp;gt; :not([id])&lt;/code&gt; bit), so let me rephrase it into words: &lt;strong&gt;Match any element with no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; if its parent is the body element and none of its siblings is the target.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I also changed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:target&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body &amp;gt; :target&lt;/code&gt; even though in this case it doesn’t change anything, to make it more parallel with the other selector. (It will also come in handy during the next step.)&lt;/p&gt;

&lt;p&gt;Now, refresh the page, and you should now find that the initial passage is shown large when no other passage is!&lt;/p&gt;

&lt;h2 id=&quot;hiding-information&quot;&gt;Hiding Information&lt;/h2&gt;

&lt;p&gt;So it’s now possible for the player to tell what passage they’re on, which is a good start. But we’d also like that to be the &lt;em&gt;only&lt;/em&gt; passage that’s visible. After all, right now you can cheat by seeing into the future. You can even click a link in any passage the same as if it were the active one!&lt;/p&gt;

&lt;p&gt;To fix that, we’ll need to style all the passages that &lt;em&gt;aren’t&lt;/em&gt; the active one, like so:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:has&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:not&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:not&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:has&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What changed? Let’s go over each part:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body:has(&amp;gt; :target) &amp;gt; :not(:target)&lt;/code&gt; – The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; part is more specific than before. Actually, the reason it wasn’t this specific before is because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body:has(&amp;gt; :target) &amp;gt; :target&lt;/code&gt; is redundant. Of &lt;em&gt;course&lt;/em&gt; any body element directly containing the target contains the target! But now that our condition has been inverted – we want any element that &lt;em&gt;isn’t&lt;/em&gt; the target – it needs to be this specific or else the initial passage will &lt;em&gt;always&lt;/em&gt; be hidden. This part of the selector could be worded as: &lt;strong&gt;Match any sibling of the target if their parent is the body element.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body:not(:has(&amp;gt; :target)) &amp;gt; [id]&lt;/code&gt; – This part, on the other hand, was already specific enough in that regard, so all that was needed was to remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:not&lt;/code&gt; to invert it. In words: &lt;strong&gt;Match any element with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; if its parent is the body element and none of its siblings is the target.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display: none&lt;/code&gt; – The matching elements are hidden and removed from the page flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refresh the page and see how it plays. With any luck, you should be seeing only one passage at a time now!&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;With this latest iteration of the CSS, the goal of creating a basic choice-based story is accomplished! Each passage is shown when it should be, and not shown when it shouldn’t. But what else could we do with this?&lt;/p&gt;

&lt;p&gt;For starters, the URL hash is not the only way a web page can track state without involving JavaScript. For instance, a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/radio&quot;&gt;radio button&lt;/a&gt;’s state can be matched with a CSS selector, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:checked&quot;&gt;:checked&lt;/a&gt;. If you think of the URL hash as a single “variable” of state, adding in input elements allows you to have multiple!&lt;sup id=&quot;fnref:input&quot;&gt;&lt;a href=&quot;#fn:input&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Instead of going directly in the body, the passages could go in any element at all. To do so, change both occurrences of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; in the CSS to whatever is appropriate. E.g., if you want to put them in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; element somewhere inside the body, change them both to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;. That way, you can have a sidebar or the like that persists even as you change passages.&lt;/p&gt;

&lt;p&gt;And of course, you probably want to style the page further to your tastes.&lt;/p&gt;

&lt;p&gt;That’s all for now. Feel free to use the HTML and CSS in this post as a template for making your own diversions. Enjoy!&lt;/p&gt;

&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:pet&quot;&gt;
      &lt;p&gt;In fact, if you have access to Grundo’s Cafe, you can see I’ve already done this on &lt;a href=&quot;https://www.grundos.cafe/~Arity/&quot;&gt;Arity’s pet page&lt;/a&gt; :) That said, as of writing, it’s not a proper adventure yet. Please look forward to it. &lt;a href=&quot;#fnref:pet&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:alternative&quot;&gt;
      &lt;p&gt;Alternatively, we could put a class on all of them and select based on that, but I prefer doing it structurally because it makes the HTML less noisy. On top of that, Grundo’s Cafe has a limit on how much HTML you can include on a pet page, so less redundant information is better. &lt;a href=&quot;#fnref:alternative&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:input&quot;&gt;
      &lt;p&gt;Regrettably, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt; elements are &lt;em&gt;not&lt;/em&gt; part of the subset of HTML allowed on Grundo’s Cafe pet pages. &lt;a href=&quot;#fnref:input&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
            <pubDate>Wed, 24 Dec 2025 02:27:00 -0500</pubDate>
            <link>https://soupdilemma.neocities.org//blog/2025/12/24/choice-based-stories-using-css.html</link>
            <guid isPermaLink="true">https://soupdilemma.neocities.org//blog/2025/12/24/choice-based-stories-using-css.html</guid>
        </item>
        
        <item>
            <title>Musical challenges and a new mini-album</title>
            <description>&lt;p&gt;This is a personal musing on music. If you want to get right to the point, &lt;a href=&quot;https://www.youtube.com/playlist?list=PLlInlSSR35vp4MoqwGShrgPA9SaQiZOYf&quot; target=&quot;_blank&quot;&gt;here is the link to Challenges on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#my-motivation-to-make-music&quot; id=&quot;markdown-toc-my-motivation-to-make-music&quot;&gt;My motivation to make music&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#chiptune&quot; id=&quot;markdown-toc-chiptune&quot;&gt;Chiptune&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tracks&quot; id=&quot;markdown-toc-tracks&quot;&gt;Tracks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#final-words&quot; id=&quot;markdown-toc-final-words&quot;&gt;Final Words&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#footnotes&quot; id=&quot;markdown-toc-footnotes&quot;&gt;Footnotes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;my-motivation-to-make-music&quot;&gt;My motivation to make music&lt;/h2&gt;

&lt;p&gt;After realizing last summer that I hadn’t made nearly enough music lately&lt;sup id=&quot;fnref:music&quot;&gt;&lt;a href=&quot;#fn:music&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, I challenged myself to post five chiptune pieces as one of my bingo tiles&lt;sup id=&quot;fnref:bingo&quot;&gt;&lt;a href=&quot;#fn:bingo&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. I picked chiptune in particular because I have ambitions&lt;sup id=&quot;fnref:ambitions&quot;&gt;&lt;a href=&quot;#fn:ambitions&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; of one day making a Game Boy game.&lt;/p&gt;

&lt;p&gt;I decided to motivate and challenge myself by asking others for sequences of notes which I would have to use all in a row in the melody. One of the reasons for this is that I find working with constraints to be really useful for stoking the creative flames, which is also one of the reasons I picked chiptune in particular.&lt;/p&gt;

&lt;p&gt;The other point of using notes someone else provided was to explore musical scales outside of my comfort zone. The notes tended to strongly suggest a key&lt;sup id=&quot;fnref:suggestion&quot;&gt;&lt;a href=&quot;#fn:suggestion&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. I can easily play a beautiful piece in A minor, which is all white keys on a keyboard, and had done just that so many times with such fervor that I cracked the A keys on my keyboard on multiple occasions. But could I play in E minor, another relatively easy key? What about A &lt;em&gt;sharp&lt;/em&gt; minor?&lt;sup id=&quot;fnref:minor&quot;&gt;&lt;a href=&quot;#fn:minor&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The truth is that I could, I just needed to give myself room to explore and make mistakes. Woven into this journey to write five chiptune pieces was also the occasional impromptu piano improvisation session over voice chat, which I did on a Discord server where I felt comfortable expressing myself creatively&lt;sup id=&quot;fnref:piano&quot;&gt;&lt;a href=&quot;#fn:piano&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;. This began when I streamed working on a piece and then decided to play piano afterwards, and my friends present enjoyed it so much that the piano became the main point of @ing everyone.&lt;/p&gt;

&lt;h2 id=&quot;chiptune&quot;&gt;Chiptune&lt;/h2&gt;

&lt;p&gt;The pieces I made are the first complete pieces I composed in Furnace tracker&lt;sup id=&quot;fnref:tracker&quot;&gt;&lt;a href=&quot;#fn:tracker&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;. Working with a tracker is very different from working with a score. If the score is for conveying the composer’s intent, and it’s up to the instrument player(s) to interpret that intent every time they perform the piece, then trackers are closer to programming the player, who is a computer, than they are to writing down the score.&lt;/p&gt;

&lt;p&gt;Chiptune is also more like low-resolution pixel art than like painting, in that, often, you have no choice but to &lt;em&gt;suggest&lt;/em&gt; detail in lieu of actually including the relevant details. With Game Boy in particular, which can play at most three tones at once&lt;sup id=&quot;fnref:tones&quot;&gt;&lt;a href=&quot;#fn:tones&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; (plus noise), sometimes it’s necessary to use illusions to convey what you want&lt;sup id=&quot;fnref:illusions&quot;&gt;&lt;a href=&quot;#fn:illusions&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h2 id=&quot;tracks&quot;&gt;Tracks&lt;/h2&gt;

&lt;p&gt;The album is appropriately named Challenges and contains five tracks, which I ordered by when I made them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Dance for me, puppet!&lt;/em&gt;&lt;/strong&gt; was the first of the bunch and is the shortest, iterating three times on a melody and then ending (or looping if you like). I was to use the notes A G# A E Eb D A in it. I played with a different melody before settling on this one, but it ended up sounding close to Megalovania and I wanted to see if I could figure out one that didn’t sound like Megalovania at all. I do believe I succeeded; A friend told me it sounds like it belongs in a Wario game. The span of time from receiving the notes to finishing the piece was about 18 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A Night For Ghosts&lt;/em&gt;&lt;/strong&gt; is the second track, a tango, and is designed to loop. You can hear it go through the main melodies twice before fading out in the version I uploaded. This one had perhaps the most challenging notes, C D D# F# A A# B. This highly chromatic sequence greatly limited the places where it could sound natural and fit in. A friend told me it evoked ghosts dancing with roses held between their teeth, which is how it got its name. Its creation spanned about 15 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;where our bodies meet the sea&lt;/em&gt;&lt;/strong&gt; is an &lt;em&gt;immediately&lt;/em&gt; in-your-face metal piece tinged with melancholy. Either it or the final track is the one I’m proudest of. I was given the notes F F C F E F Bb, which are very easy to work with and suggest a major key. I did not go with a major key. As a joke, I was separately given the notes F F F F F F, so, as a joke, I included them as well. There are around five distinct sections with about three different feels to them. I almost cut the second section due to being so very different from the others, but now I can’t imagine the song without it. This one took me significantly shorter than the others to finish, 4 days, which was because I was very motivated and worked on it for hours per day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Before All Hope Is Lost&lt;/em&gt;&lt;/strong&gt; was conceptualized as the music for a final boss’s second phase. It incorporates the same organ voice as &lt;em&gt;Dance for me, puppet!&lt;/em&gt; and uses it very differently. The notes to include were F Bb F D F G# A, which are a little tucked away compared to the others so far but still appear in the melody. The consensus was that the organ-centered section, the “second verse” so to speak, is the most interesting part. This one took me the longest due to numerous troubles and a couple false starts, spanning 23 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The Power to Change&lt;/em&gt;&lt;/strong&gt; is another metal piece, even more ambitious in some ways than &lt;em&gt;where our bodies meet the sea&lt;/em&gt;, and by far the longest at a little over four minutes. It doesn’t merely have sections, it also conceptually has two &lt;em&gt;parts&lt;/em&gt;, which are divided by silence but very much linked. This one has lyrics! However, I threw them out so that listeners could decide what they are for themselves. This song’s creation spanned 17 days.&lt;/p&gt;

&lt;p&gt;Oh, did I forget to mention the required notes? They were E E D D C. (Those are the notes of “E-I-E-I-O”.) They were fairly difficult to include in the piece, because I made the decision to write the majority of it in A# minor, a scale that includes only one of those notes. Regardless, I managed to fit them in twice, in two different figures, in a way that I think makes sense.&lt;/p&gt;

&lt;h2 id=&quot;final-words&quot;&gt;Final Words&lt;/h2&gt;

&lt;p&gt;I feel like I learned something new with each piece, and they were all fun to make :) I even loop them when I shower, either all in sequence or individual ones. I think it definitely says that I made music I liked!&lt;/p&gt;

&lt;p&gt;I’m looking forward to making even more music I like soon. Actually, I’m thinking of learning to play with the expanded capabilities that the Game Boy Advance sound chip offers over the Game Boy. It has the same channels plus two sample channels&lt;sup id=&quot;fnref:samples&quot;&gt;&lt;a href=&quot;#fn:samples&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;, which might not sound like much but greatly expands the possibility space. I’ll be challenging myself further to take full advantage of this :)&lt;/p&gt;

&lt;p&gt;Well, that’s about all the words I’ve got for now&lt;sup id=&quot;fnref:bandcamp&quot;&gt;&lt;a href=&quot;#fn:bandcamp&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;! Now go, &lt;a href=&quot;https://www.youtube.com/playlist?list=PLlInlSSR35vp4MoqwGShrgPA9SaQiZOYf&quot;&gt;enjoy the album&lt;/a&gt;!&lt;/p&gt;

&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:music&quot;&gt;
      &lt;p&gt;A realization spurred by watching Girls Band Cry. I enjoyed it. My main critique of it is the same as for Bocchi the Rock: We’re told the music has certain flaws that the track we actually hear doesn’t have. &lt;a href=&quot;#fnref:music&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:bingo&quot;&gt;
      &lt;p&gt;The rules are: Come up with 24 tasks you want to do this year, then shuffle them and arrange them on a 5×5 board with a free space in the center. If you complete any line of five tasks (or four tasks and the free space) then you get a bingo. I like it because I get to feel accomplished even though I didn’t do every single thing :) &lt;a href=&quot;#fnref:bingo&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ambitions&quot;&gt;
      &lt;p&gt;Vague ideas pingponging around in my head, backed by vague notes. &lt;a href=&quot;#fnref:ambitions&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:suggestion&quot;&gt;
      &lt;p&gt;although sometimes I ignored the suggestion :) &lt;a href=&quot;#fnref:suggestion&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:minor&quot;&gt;
      &lt;p&gt;For whatever reason, I find it easier to be creative in minor keys. Every track on the album is in a minor key of some variety. &lt;a href=&quot;#fnref:minor&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:piano&quot;&gt;
      &lt;p&gt;At one point during this, I expressed something along the lines of “I don’t think I’m very good at piano” to a friend. What a silly thing to say! I was the only one who was hearing me play and didn’t think I was all that good. But don’t worry, I got better! (at recognizing how good I am) &lt;a href=&quot;#fnref:piano&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:tracker&quot;&gt;
      &lt;p&gt;I’ve used trackers relatively little thus far. I made one piece in Pixitracker, but that really has more in common with Mario Paint than with what I’ve come to expect of chiptune trackers like Furnace or MilkyTracker. Which is not to say that it can’t make beautiful music, so much as that its expression space is not necessarily what I’m interested in when I say “chiptune tracker”. I also made a BGM in PICO-8 for &lt;a href=&quot;https://soupdilemma.itch.io/tiny-terrarium&quot; target=&quot;_blank&quot;&gt;tiny terrarium&lt;/a&gt;. &lt;a href=&quot;#fnref:tracker&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:tones&quot;&gt;
      &lt;p&gt;Depending on how you interpret “at once”. Plus, it’s possible to create a wavetable that plays octaval unison. I’m not sure if other intervals are possible. &lt;a href=&quot;#fnref:tones&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:illusions&quot;&gt;
      &lt;p&gt;My favorite video on this topic is &lt;a href=&quot;https://www.youtube.com/watch?v=OPv6ohjF1-s&quot; target=&quot;_blank&quot;&gt;Ya got TRICKED: That Full Band was really just 3 Channels!&lt;/a&gt; &lt;a href=&quot;#fnref:illusions&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:samples&quot;&gt;
      &lt;p&gt;Do you know about the “Hoenn trumpets” in Pokémon Ruby and Sapphire? Those are an example of something that the sample channels allow! &lt;a href=&quot;#fnref:samples&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:bandcamp&quot;&gt;
      &lt;p&gt;By the way, I want to put these on Bandcamp too. The only reason I haven’t yet is because they’re phasing out support for PayPal in favor of Stripe, and I would prefer not to make a new account on PayPal just to have to also make one on Stripe shortly after. Please look forward to it! &lt;a href=&quot;#fnref:bandcamp&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
            <pubDate>Wed, 03 Sep 2025 01:22:00 -0400</pubDate>
            <link>https://soupdilemma.neocities.org//blog/2025/09/03/musical-challenges-and-a-new-mini-album.html</link>
            <guid isPermaLink="true">https://soupdilemma.neocities.org//blog/2025/09/03/musical-challenges-and-a-new-mini-album.html</guid>
        </item>
        
        <item>
            <title>Low-effort WebSocket encryption using nginx</title>
            <description>&lt;p&gt;This is a post on adding encryption to a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/WebSockets&quot; target=&quot;_blank&quot;&gt;WebSocket&lt;/a&gt; server without needing to change its code, using &lt;a href=&quot;https://nginx.org/&quot; target=&quot;_blank&quot;&gt;nginx&lt;/a&gt;. Some command line knowledge is assumed.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#background&quot; id=&quot;markdown-toc-background&quot;&gt;Background&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#prerequisites&quot; id=&quot;markdown-toc-prerequisites&quot;&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#serving-a-static-website&quot; id=&quot;markdown-toc-serving-a-static-website&quot;&gt;Serving a static website&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#proxying-a-websocket-server&quot; id=&quot;markdown-toc-proxying-a-websocket-server&quot;&gt;Proxying a WebSocket server&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#adding-encryption&quot; id=&quot;markdown-toc-adding-encryption&quot;&gt;Adding encryption&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#appendix&quot; id=&quot;markdown-toc-appendix&quot;&gt;Appendix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Recently, I was looking into making a MUD, and decided I wanted to do it with a WebSocket service. Since it’s usual for modern web browsers to encrypt connections, I also wanted to make sure it supported TLS.&lt;/p&gt;

&lt;p&gt;I decided to write the server in Haskell, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;websockets&lt;/code&gt; package I saw recommended didn’t support TLS, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wuss&lt;/code&gt; package it suggested to add TLS support didn’t support servers! So, I did a web search to see if anyone else had run into this problem.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27842690/secure-websockets-with-happstack/27878405#27878405&quot; target=&quot;_blank&quot;&gt;One StackOverflow answer&lt;/a&gt; suggested adding TLS after the fact using nginx. This sounded much more convenient than having to deal with TLS logic myself, so that’s what I set out to do, and what I’ll explain how I did in this post.&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;You’ll need to have &lt;a href=&quot;https://nginx.org/en/docs/install.html&quot; target=&quot;_blank&quot;&gt;nginx&lt;/a&gt; installed.&lt;/p&gt;

&lt;p&gt;You’ll also need a WebSocket server. For the sake of demonstration, we’ll use &lt;a href=&quot;https://github.com/vi/websocat/&quot; target=&quot;_blank&quot;&gt;websocat&lt;/a&gt;, even though it already supports encryption.&lt;/p&gt;

&lt;p&gt;On Arch Linux, you can install both of these from the “extra” repository with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pacman -S nginx websocat&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On Debian-based systems, you can get nginx with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt install nginx&lt;/code&gt;. It looks like the latest websocat releases as of time of writing don’t include a .deb, but &lt;a href=&quot;https://github.com/vi/websocat/releases/tag/v1.8.0&quot; target=&quot;_blank&quot;&gt;the 1.8.0 release&lt;/a&gt; does.&lt;/p&gt;

&lt;p&gt;For other OSes, check the links above for instructions.&lt;/p&gt;

&lt;h2 id=&quot;serving-a-static-website&quot;&gt;Serving a static website&lt;/h2&gt;

&lt;p&gt;Let’s start by serving up a single HTML file.&lt;/p&gt;

&lt;p&gt;As a bonus requirement, I decided I wanted to be able to run nginx unprivileged (not as root), so let’s make a new directory somewhere convenient and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt; into it.&lt;/p&gt;

&lt;p&gt;In that directory, make a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt;. We’ll add to it over the course of the post, but for now just add the following to it:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;daemon&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will cause nginx to stay attached to the terminal you run it from, for ease of iteration.&lt;/p&gt;

&lt;p&gt;Now let’s make the file we’re going to serve. In that same directory, make another directory called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static&lt;/code&gt; (this name has no special significance) and a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/index.html&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;hello nginx!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;hello nginx!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Don’t worry about the nonexistent script for now, we’ll get to it.)&lt;/p&gt;

&lt;p&gt;After a quick glance through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man nginx&lt;/code&gt;, it looks like we can use the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx -p ./ -c nginx.conf&lt;/code&gt; to run the server from this directory.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p ./&lt;/code&gt; says to look for the configuration and other files in the present working directory.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-c nginx.conf&lt;/code&gt; says to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt; as the configuration file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we try to run it now, nginx should complain that there’s no “events” section. Let’s add this empty one to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running again, we get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open() &quot;/run/nginx.pid&quot; failed&lt;/code&gt;. Add:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx.pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now it should run without errors, but… it doesn’t &lt;em&gt;do&lt;/em&gt; anything yet! That’s because we haven’t actually told nginx to be an HTTP server.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://nginx.org/en/docs/beginners_guide.html#static&quot; target=&quot;_blank&quot;&gt;Beginner’s Guide&lt;/a&gt; has a perfectly good section on this, whose advice we’ll adapt to our requirements:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running it now gives &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open() &quot;/var/log/nginx/access.log&quot; failed&lt;/code&gt;, so we’ll add another directive &lt;strong&gt;just inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http&lt;/code&gt; block&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;access_log&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;access.log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You could also just as well put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/null&lt;/code&gt; in place of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access.log&lt;/code&gt; if you don’t want to log incoming connections to a file.&lt;/p&gt;

&lt;p&gt;Now run it one last time. It should &lt;em&gt;stay&lt;/em&gt; running now. Has our hard work finally paid off? Use a browser to visit &lt;a href=&quot;http://localhost:8000/&quot; target=&quot;_blank&quot;&gt;http://localhost:8000/&lt;/a&gt; and find out! (We didn’t specify it, but 8000 is the default port, per &lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_core_module.html#listen&quot; target=&quot;_blank&quot;&gt;the documentation&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;It loaded a page, right? If so, great! On to the next part! Otherwise, double check that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt; looks something like this:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;daemon&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx.pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;access_log&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;access.log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;proxying-a-websocket-server&quot;&gt;Proxying a WebSocket server&lt;/h2&gt;

&lt;p&gt;Next we’ll add WebSocket support to our server. There are some special considerations, which fortunately are covered by the &lt;a href=&quot;https://nginx.org/en/docs/http/websocket.html&quot; target=&quot;_blank&quot;&gt;WebSocket proxying&lt;/a&gt; page in the nginx documentation.&lt;/p&gt;

&lt;p&gt;Let’s try using the first example there almost verbatim. Add this &lt;strong&gt;inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server&lt;/code&gt; block&lt;/strong&gt;, i.e., at the same level as the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;location&lt;/code&gt; block:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        &lt;span class=&quot;k&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/chat/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://0.0.0.0:8001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upgrade&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;b&gt;Note&lt;/b&gt;: If you plan for this website to face the public internet, you will &lt;em&gt;not&lt;/em&gt; be allowing traffic to port 8001, because we won’t be encrypting that one!&lt;/p&gt;

&lt;p&gt;If you try restarting the server and accessing &lt;a href=&quot;http://localhost:8000/chat&quot; target=&quot;_blank&quot;&gt;http://localhost:8000/chat&lt;/a&gt; at this point, you should get “502 Bad Gateway”. This means it’s working! Sort of. The backend we told it to redirect to isn’t up.&lt;/p&gt;

&lt;p&gt;While nginx is still running, start &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;websocat -s 8001&lt;/code&gt;. Then refresh the page and you should get “Only WebSocket connections are welcome here”. This is websocat talking! Now we just need to use it with actual WebSockets.&lt;/p&gt;

&lt;p&gt;Now we return to that missing script from earlier. Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/main.js&lt;/code&gt; with the following contents:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ws://&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/chat/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// When we successfully connect, send &quot;hello, server!&quot;.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hello, server!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// When we get a message:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// - Add it to the page body.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// - If it contains &quot;ping&quot;, repeat it back but with all instances of &quot;ping&quot;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// replaced with &quot;pong&quot;.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ping/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;pong&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// As a bonus treat, the New Nintendo 3DS web browser can run this code fine!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, reload &lt;a href=&quot;http://localhost:8000/&quot; target=&quot;_blank&quot;&gt;http://localhost:8000/&lt;/a&gt; and check the output of websocat. You should see “hello, server!” has been received. Try typing various things into websocat’s terminal and watch them appear on the web page! You can even have multiple instances of the page open and they should all receive what you enter.&lt;/p&gt;

&lt;p&gt;Your browser console may complain that the script is served as the wrong MIME type. This is probably not a fatal issue, but if it bothers you, add this to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt; &lt;strong&gt;just inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http&lt;/code&gt; block&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/etc/nginx/mime.types&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’re almost there! Your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt; should now look like this:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;daemon&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx.pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/etc/nginx/mime.types&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;access_log&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;access.log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/chat/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://0.0.0.0:8001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upgrade&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;adding-encryption&quot;&gt;Adding encryption&lt;/h2&gt;

&lt;p&gt;Right now, the website, including the WebSocket service, are served unencrypted. Our final step will be to add TLS to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Encryption is really easy to mess up. I’m by no means an expert, so please consult other resources for best practices!&lt;/p&gt;

&lt;p&gt;For purposes of this post, we’ll be generating a self-signed certificate. &lt;strong&gt;This isn’t good enough for a public website&lt;/strong&gt;, because your browser will tell you it doesn’t trust the certificate, and it would defeat the point if your visitors ignored this warning! For that, you’ll likely want to use &lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot;&gt;Let’s Encrypt&lt;/a&gt; or the like, which I won’t cover here.&lt;/p&gt;

&lt;p&gt;Now that that warning is out of the way, let’s run the following command to generate what we need, which I cobbled together by looking at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man openssl-req&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl req &lt;span class=&quot;nt&quot;&gt;-x509&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-nodes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-subj&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; certificate.pem &lt;span class=&quot;nt&quot;&gt;-keyout&lt;/span&gt; key.pem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This creates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;certificate.pem&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key.pem&lt;/code&gt; in the current directory. Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key.pem&lt;/code&gt; is a &lt;strong&gt;private key without a passphrase&lt;/strong&gt;. As a rule, if someone else gets a copy of your private key, you should replace it as soon as possible.&lt;/p&gt;

&lt;p&gt;Now we need to edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt; once more. Once again, &lt;a href=&quot;https://nginx.org/en/docs/http/configuring_https_servers.html&quot; target=&quot;_blank&quot;&gt;the nginx documentation&lt;/a&gt; has some guidance. &lt;strong&gt;Just inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server&lt;/code&gt; block&lt;/strong&gt;, add:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        &lt;span class=&quot;k&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8000&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ssl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;ssl_certificate&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;certificate.pem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;ssl_certificate_key&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;key.pem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now nginx will use HTTPS protocol on that port, but there’s one other thing: We have to change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;ws://&apos;&lt;/code&gt; in the JavaScript code to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;wss://&apos;&lt;/code&gt; to tell it to use &lt;em&gt;secure&lt;/em&gt; WebSocket.&lt;/p&gt;

&lt;p&gt;Restart nginx once more, double check that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;websocat -s 8001&lt;/code&gt; is running, and then visit &lt;a href=&quot;https://localhost:8000/&quot; target=&quot;_blank&quot;&gt;https://localhost:8000/&lt;/a&gt;. Your browser should warn you that the certificate isn’t trusted (If it doesn’t, consider switching to one that does!), but go ahead and click through anyway.&lt;/p&gt;

&lt;p&gt;You should find… that nothing about the page changed! Mission accomplished!&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;As suggested by the StackOverflow answer, it was possible to take an unencrypted WebSocket service and add TLS support to it without fundamentally changing any of its logic; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;websocat -s 8001&lt;/code&gt; was the only command we used to run that part of the setup.&lt;/p&gt;

&lt;p&gt;Some things could be improved:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As mentioned, self-signed certificates are generally considered untrusted by browsers. It would be best to have one signed by a certificate authority such as Let’s Encrypt.&lt;/li&gt;
  &lt;li&gt;It’s generally less confusing to serve on the standard port, which for HTTPS is 443. I chose to use a different port just so I could quickly test as a non-root user, since Linux blocks use of ports up to 1024 by ordinary users.&lt;/li&gt;
  &lt;li&gt;At least for me, nginx complains that it “could not build optimal types_hash”. As this didn’t actually affect any functionality, I didn’t feel it needed addressing in this post, but nginx does helpfully provide instructions on how to address it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And of course, the HTML and JS we used were placeholders, as was websocat.&lt;/p&gt;

&lt;p&gt;Ultimately my plan is to make a multiplayer browser game and, with this, I’m one step closer!&lt;/p&gt;

&lt;p&gt;Thanks to redwing for beta reading this post!&lt;/p&gt;

&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;

&lt;p&gt;Here are the final states of all the files.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx.conf&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;daemon&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx.pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/etc/nginx/mime.types&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;access_log&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;access.log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8000&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ssl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;ssl_certificate&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;certificate.pem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;ssl_certificate_key&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;key.pem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/chat/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://0.0.0.0:8001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upgrade&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/index.html&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;hello nginx!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;hello nginx!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/main.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;wss://&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/chat/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// When we successfully connect, send &quot;hello, server!&quot;.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hello, server!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// When we get a message:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// - Add it to the page body.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// - If it contains &quot;ping&quot;, repeat it back but with all instances of &quot;ping&quot;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// replaced with &quot;pong&quot;.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ping/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;pong&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// As a bonus treat, the New Nintendo 3DS web browser can run this code fine!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;certificate.pem&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key.pem&lt;/code&gt; don’t have fixed contents. Instead, they’re generated by this command:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl req &lt;span class=&quot;nt&quot;&gt;-x509&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-nodes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-subj&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; certificate.pem &lt;span class=&quot;nt&quot;&gt;-keyout&lt;/span&gt; key.pem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
            <pubDate>Wed, 16 Oct 2024 14:21:00 -0400</pubDate>
            <link>https://soupdilemma.neocities.org//blog/2024/10/16/low-effort-websocket-encryption-using-nginx.html</link>
            <guid isPermaLink="true">https://soupdilemma.neocities.org//blog/2024/10/16/low-effort-websocket-encryption-using-nginx.html</guid>
        </item>
        
    </channel>
</rss>
