<?xml-stylesheet href="/pretty-feed-v2.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Trys Mudford's Blog</title>
    <link>https://www.trysmudford.com/tags/side-project/</link>
    <description>Posts, thoughts, links and photos from Trys</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 17 Dec 2020 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.trysmudford.com/blog/index.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title>Everyday I&#39;m Shuffling</title>
      <link>https://www.trysmudford.com/blog/everyday-im-shuffling/</link>
      <pubDate>Thu, 17 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/everyday-im-shuffling/</guid>
      <description><![CDATA[
<p>I&rsquo;ve been thinking about shuffling cards and arrays. That might sound odd, but having built a fair few <a href="https://www.trysmudford.com/blog/virtual-games-boggle/">spreadsheet games</a>, and played an awful lot of cards this year, I guess it makes sense.</p>
<p>Let&rsquo;s start with shuffling a deck of cards. This classic move is the riffle and bridge, or so I&rsquo;m told:</p>
<figure>
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
    <video src="https://www.trysmudford.com/images/blog/shuffle-small.mp4" poster="https://www.trysmudford.com/images/blog/shuffle-poster.jpg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" preload="none" controls></video>
  </div>
  <br />
  <figcaption>Yes - I'm not very good at this shuffle yet...</figcaption>
</figure>
<p>It involves splitting the deck in two, and fanning the two halves back together from the bottom of each side, card by card. But I was wondering, assuming you did a perfect shuffle:</p>
<blockquote>
<p>How long would it take to get the deck back to the first, initial state? Could you unshuffle the shuffle?</p>
</blockquote>
<p>Now as you&rsquo;ve seen from the above video, I&rsquo;m not nearly a good enough shuffler to do this perfectly in real life, but I can code (replace shuffler with pretty much anything, and this statement sums up my life).</p>
<p>Let&rsquo;s write a bit of JavaScript. A traditional deck is made of 52 cards, so we can create an array with numbers: 1-52 with the <code>Array.from()</code> method.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">cards</span> <span class="o">=</span> <span class="mi">52</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">deck</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">({</span> <span class="nx">length</span><span class="o">:</span> <span class="nx">cards</span> <span class="p">},</span> <span class="p">(</span><span class="mi">_</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</code></pre></div><p>Next we have a method that splits the deck in two, empties the original pack, and pushes the cards back in, card by card.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">shuffleIn</span><span class="p">()</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">half1</span> <span class="o">=</span> <span class="nx">deck</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">cards</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span>
  <span class="kr">const</span> <span class="nx">half2</span> <span class="o">=</span> <span class="nx">deck</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">cards</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">cards</span><span class="p">);</span>
  <span class="nx">deck</span> <span class="o">=</span> <span class="p">[];</span>
  
  <span class="nx">half2</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">card</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">deck</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">card</span><span class="p">,</span> <span class="nx">half1</span><span class="p">[</span><span class="nx">index</span><span class="p">]));</span>
<span class="p">}</span>

<span class="nx">shuffleIn</span><span class="p">();</span>
</code></pre></div><p>When we output the result, we can see what&rsquo;s happening:</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">[26, 52, 25, 51, 24, 50, 23, 49, 22, 48, 21...]
</code></pre></div><p>The odd-position numbers are the original top half of the pack, and the even-positions are the bottom, and both positions are descending. Running it a second time on the newly created deck produces the following:</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">40, 27, 14, 1, 41, 28, 15, 2, 42, 29, 16, 3...]
</code></pre></div><p>It&rsquo;s looking more random, but we still have a pattern: every fourth card is ascending. So, the big question, how many shuffles till we get back around to the same pack? 20, 40, 80? Let&rsquo;s run it 100 times, and stop when it looks like the pattern is back to the beginning:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">shuffleIn</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">deck</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">===</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="nx">deck</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">deck</span><span class="p">,</span> <span class="sb">`Shuffle no. </span><span class="si">${</span><span class="nx">i</span> <span class="o">-</span> <span class="mi">1</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
    <span class="k">break</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>The answer? <strong>Shuffle no. 52</strong>.</p>
<p>That seems like a reasonable number given the number of cards. And it doesn&rsquo;t seem like the sort of problem we&rsquo;d run into in the real world, right?</p>
<p>But what if we made one small change. Let&rsquo;s swap the halves round, so we push in the top deck first. All we&rsquo;re changing is the final line here:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">shuffleOut</span><span class="p">()</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">half1</span> <span class="o">=</span> <span class="nx">deck</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">cards</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span>
  <span class="kr">const</span> <span class="nx">half2</span> <span class="o">=</span> <span class="nx">deck</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">cards</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">cards</span><span class="p">);</span>
  <span class="nx">deck</span> <span class="o">=</span> <span class="p">[];</span>
  
  <span class="nx">half1</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">card</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">deck</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">card</span><span class="p">,</span> <span class="nx">half2</span><span class="p">[</span><span class="nx">index</span><span class="p">]));</span>
<span class="p">}</span>
</code></pre></div><p>The answer? <strong>Shuffle no. 8</strong> 😳</p>
<p>Goodness! I wasn&rsquo;t expecting it to be that low! That seems like a similar number to a pretty &lsquo;average&rsquo; shuffle in my experience. Interestingly, 7 is the number the <a href="https://en.wikipedia.org/wiki/Gilbert%E2%80%93Shannon%E2%80%93Reeds_model">Gilbert–Shannon–Reeds model</a> arrives at to generate a thoroughly randomised deck.</p>
<p>So depending on which way you shuffle a pack, you could end up with <em>very</em> different results.</p>
<h3 id="other-noteworthy-findings">Other noteworthy findings</h3>
<p>With the <code>shuffleIn</code> method, we arrive at the original order in reverse on shuffle no. 26:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">Shuffle</span> <span class="mi">26</span><span class="o">:</span> <span class="p">[</span> <span class="mi">52</span><span class="p">,</span> <span class="mi">51</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">49</span><span class="p">,</span> <span class="mi">48</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">46</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="p">...]</span>
</code></pre></div><p>You can even begin to see the convergent pattern as you get closer to 52, too:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">Shuffle</span> <span class="mi">49</span><span class="o">:</span> <span class="p">[</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">48</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="p">...]</span>
<span class="nx">Shuffle</span> <span class="mi">50</span><span class="o">:</span> <span class="p">[</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="p">...</span> <span class="p">]</span>
<span class="nx">Shuffle</span> <span class="mi">51</span><span class="o">:</span> <span class="p">[</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="p">...]</span>
<span class="nx">Shuffle</span> <span class="mi">52</span><span class="o">:</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="p">...]</span>
</code></pre></div><h2 id="shuffling-arrays">Shuffling arrays</h2>
<p>Okay, onto shuffling arrays in JS. A cursory google with Bing for &lsquo;randomly sort array js&rsquo; will return a bit of code a bit like this:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">array</span><span class="p">.</span><span class="nx">sort</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">);</span>
</code></pre></div><p>If we un-minify it a little, we can see what&rsquo;s happening:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">array</span><span class="p">.</span><span class="nx">sort</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">-</span><span class="p">[]()</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="p">});</span>
</code></pre></div><p><code>array.sort()</code> runs a comparison function that compares two items, normally items <code>a</code> and <code>b</code>. You return a positive or negative value to indicate whether item <code>a</code> is bigger or smaller than item <code>b</code>.</p>
<p>But in this &lsquo;random&rsquo; function, instead of using the items themselves, it calls <code>Math.random()</code>; that returns a random number between 0 and 1, say: <code>0.7395929326168424</code>. If you minus 0.5 from that, it&rsquo;ll be a positive, or a negative number, achieving the same result.</p>
<p>In theory, random. In reality, flawed.</p>
<p>Let&rsquo;s run the above in an experiment where we plot the distribution of the first card in a deck (call it the Ace of Hearts).</p>
<p>We&rsquo;ll start by constructing a deck. It&rsquo;s similar to before, but this time, we&rsquo;re going to get a fresh deck every time we call it. We&rsquo;ll also create a &lsquo;distribution&rsquo; object with keys running from 0 - 51, all set with a starting point of 0.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">deck</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">({</span> <span class="nx">length</span><span class="o">:</span> <span class="mi">52</span> <span class="p">},</span> <span class="p">(</span><span class="mi">_</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">distribution</span> <span class="o">=</span> <span class="p">{};</span>
<span class="nx">deck</span><span class="p">().</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">l</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">distribution</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div><p>Next we&rsquo;ll pop in the shuffle function, and run it 10,000 times, plotting the <code>indexOf</code> position for card 0 every time we loop around. Finally, we&rsquo;ll log the results. (For the record, this is running in Node 11).</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">shuffle</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">array</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">arr</span><span class="p">];</span>
  <span class="nx">array</span><span class="p">.</span><span class="nx">sort</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span>
  <span class="k">return</span> <span class="nx">array</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">10000</span><span class="p">;</span> <span class="nx">index</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">letters</span> <span class="o">=</span> <span class="nx">shuffle</span><span class="p">(</span><span class="nx">deck</span><span class="p">());</span>
  <span class="nx">distribution</span><span class="p">[</span><span class="nx">letters</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="mi">1</span><span class="p">)]</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">distribution</span><span class="p">);</span>
</code></pre></div><p>Okay, here goes:</p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 740 450" id="shufflePoor" data-shuffle="shufflePoor">
  <g fill="none" fill-rule="evenodd" class="distribution-group">
    <path fill="#EAEEF2" d="M0 0h740v450H0z"/>
    <path stroke="#6E6E6E" stroke-linecap="square" stroke-width="3" d="M30.5 29.5L30.066 421M710.5 421.5l-680.434-.479"/>
    <text fill="#5E616B" font-family="Avenir-Roman, Avenir" font-size="16"><tspan x="50" y="441">Card no.</tspan></text><text fill="#5E616B" font-family="Avenir-Roman, Avenir" font-size="16" transform="rotate(-90 15 312.5)"><tspan x="-73.5" y="317.5">Distribution in position 0</tspan></text>
  </g>
</svg>
<button type="button" class="button" data-target="shufflePoor">Simulate</button>
<h3 id="results">Results</h3>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">&#39;0&#39;: 591,
&#39;1&#39;: 506,
&#39;2&#39;: 422,
&#39;3&#39;: 405,
&#39;4&#39;: 334,
&#39;5&#39;: 313,
...
&#39;45&#39;: 145,
&#39;46&#39;: 164,
&#39;47&#39;: 157,
&#39;48&#39;: 144,
&#39;49&#39;: 147,
&#39;50&#39;: 109,
&#39;51&#39;: 100
</code></pre></div><p>Woah there. The chances of the Ace of Hearts appearing in position 0 is <strong>six</strong> times more likely than it is appearing in position 51. That&rsquo;s a <em>huge</em> bias!</p>
<p><strong>Sidenote</strong>: Safari appears to have a reverse bias, in comparison to Chrome and Firefox.</p>
<p>To be totally honest, the only reason I even contemplated this being a problem was when I&rsquo;d foolishly implemented this method of sorting on an online version of Nomination Whist I created in a spreadsheet. After playing for many weeks in lockdown, one of the players regularly seemed to pick up the Ace of Hearts when they were &lsquo;randomly&rsquo; dealt cards, and this was why.</p>
<p>Depending on the sorting algorithm used in the browser/runtime, the items at either end of the array get compared less than those in the middle, and if the &lsquo;random&rsquo; chance is effectively 50/50 (as <code>Math.random() - 0.5 &gt; 0</code> is), there&rsquo;s a greater chance those items will stay nearer the ends.</p>
<p>To fix it, we need a better shuffle. The <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle</a> is the one to go for, as explained <a href="https://javascript.info/task/shuffle">here by javascript.info</a>.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">shuffle</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">array</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">arr</span><span class="p">];</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">array</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
    <span class="kr">const</span> <span class="nx">j</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
    <span class="p">[</span><span class="nx">array</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">array</span><span class="p">[</span><span class="nx">j</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[</span><span class="nx">array</span><span class="p">[</span><span class="nx">j</span><span class="p">],</span> <span class="nx">array</span><span class="p">[</span><span class="nx">i</span><span class="p">]]</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">array</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>Now when we run the test, the results are more far promising:</p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 740 450" id="shuffle" data-shuffle="shuffle">
  <g fill="none" fill-rule="evenodd" class="distribution-group">
    <path fill="#EAEEF2" d="M0 0h740v450H0z"/>
    <path stroke="#6E6E6E" stroke-linecap="square" stroke-width="3" d="M30.5 29.5L30.066 421M710.5 421.5l-680.434-.479"/>
    <text fill="#5E616B" font-family="Avenir LT, Avenir-Roman, Avenir, sans-serif" font-size="16"><tspan x="50" y="441">Card no.</tspan></text><text fill="#5E616B" font-family="Avenir LT, Avenir-Roman, Avenir, sans-serif" font-size="16" transform="rotate(-90 15 312.5)"><tspan x="-73.5" y="317.5">Distribution in position 0</tspan></text>
  </g>
</svg>
<button type="button" class="button" data-target="shuffle">Simulate</button>
<h3 id="results-1">Results</h3>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">&#39;0&#39;: 187,
&#39;1&#39;: 189,
&#39;2&#39;: 174,
&#39;3&#39;: 180,
&#39;4&#39;: 198,
...
&#39;46&#39;: 215,
&#39;47&#39;: 207,
&#39;48&#39;: 182,
&#39;49&#39;: 171,
&#39;50&#39;: 171,
&#39;51&#39;: 181
</code></pre></div><p>Bingo.</p>
<script>
(() => {
  const lerp = (x, y, a) => x * (1 - a) + y * a;
  const clamp = (a, min = 0, max = 1) => Math.min(max, Math.max(min, a));
  const invlerp = (x, y, a) => clamp((a - x) / (y - x));
  const range = (x1, y1, x2, y2, a) => lerp(x2, y2, invlerp(x1, y1, a));

  const shuffles = {
    shufflePoor: (arr) => {
      const array = [...arr];
      array.sort(() => Math.random() - 0.5)
      return array;
    },

    shuffle: (arr) => {
      const array = [...arr];
      for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]]
      }

      return array;
    }
  }

  const minX = 55;
  const maxX = 695;

  const minY = 400;
  const maxY = 33;

  let runningMin = 0;
  let runningMax = 1;

  const newDeck = () => Array.from({ length: 52 }, (_, i) => i);

  function plotter(svg, method) {
    const group = svg.querySelector('.distribution-group');

    const deck = Array.from({ length: 52 }, (_, i) => ({ index: i, circle: null }));
    const distribution = {};
    deck.forEach((l, i) => distribution[i] = 0);

    deck.forEach((card) => {
      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      circle.setAttribute('cx', range(0, 51, minX, maxX, card.index));
      circle.setAttribute('cy', range(runningMin, runningMax, minY, maxY, distribution[card.index]));
      circle.setAttribute('r', 5);
      circle.setAttribute('fill', '#727272');
      group.appendChild(circle);
      card.circle = circle;
    });

    return [distribution, () => plot(0, method, distribution, deck)]
  }

  function plot(i, method, distribution, deck) {
    if (i >= 1000) return;

    for (let i = 0; i < 10; i++) {
      const letters = method(newDeck());
      distribution[letters.indexOf(0)]++;
      runningMin = Math.min(...Object.values(distribution));
      runningMax = Math.max(...Object.values(distribution));

      runningMax = runningMax * 1.25;
      runningMin = runningMin * 0.5;

      deck.forEach(card => {
        card.circle.setAttribute('cy', range(runningMin, runningMax, minY, maxY, distribution[card.index]));
      })
    }

    requestAnimationFrame(() => plot(i + 1, method, distribution, deck));
  }

  Array.from(document.querySelectorAll('[data-target]')).forEach(el => {
    const svg = document.getElementById(el.getAttribute('data-target'));

    const [distribution, method] = plotter(svg, shuffles[svg.getAttribute('data-shuffle')])

    el.addEventListener('click', () => {
      newDeck().forEach((l, i) => distribution[i] = 0);
      method()
    })
  })
})();
</script>
]]>
      </description>
    </item>
    
    <item>
      <title>Making a &#39;post-it game&#39; PWA with mobile accelerometer API&#39;s</title>
      <link>https://www.trysmudford.com/blog/heads-up/</link>
      <pubDate>Thu, 02 Jan 2020 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/heads-up/</guid>
      <description><![CDATA[
<p>At the annual Clearleft Christmas Party, after a fabulous pub lunch and successful Secret Santa (courtesy of <a href="https://basil.christmas.trysmudford.com/">Basil</a>), we ordered some drinks and settled in to play some games.</p>
<p>Charades was the first candidate, but after some truly questionable suggestions &amp; acting, we thought it best to open the acting out to democracy and play the &lsquo;Heads up&rsquo; game.</p>
<p>Originally known as the <a href="https://www.theguardian.com/lifeandstyle/2008/nov/17/party-games-guide">Rizla game</a> or &lsquo;post-it&rsquo; game, the aim is for one individual to attach a post-it/cigarette paper to their forehead, and for the rest of the group to act it out. When guessed, you move onto the next clue.</p>
<p>Nowadays there is, of course, an app for that. Instead of post-its, you use your phone. And instead of swapping clues/post-its, you droop your head down, and look back up. It&rsquo;s a lot of fun, and generated a lot of laughter. But it got me thinking: why use an app, when you can use the web?! We are Clearleft, after all.</p>
<p>The next morning, while others were nursing slightly sore heads, I got to work on <a href="http://trys-heads-up.netlify.com/">building just that</a>. There were two main challenges to overcome:</p>
<ol>
<li>Make the website work offline</li>
<li>Detect when the user looks down!</li>
</ol>
<h2 id="building-a-pwa">Building a PWA</h2>
<p>I begun the project with the <a href="https://preactjs.com/cli/getting-started">Preact CLI</a>; this provided me with a progressive web app shell and a smart webpack build system.</p>
<p>Preact pre-caches routes and assets included in webpack, but I needed to get a few sound effects into the cache. I followed the <a href="https://preactjs.com/cli/service-worker/">documentation</a> on how the service worker works, and added the following snippet:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">workbox</span><span class="p">.</span><span class="nx">routing</span><span class="p">.</span><span class="nx">registerRoute</span><span class="p">(</span>
  <span class="p">({</span> <span class="nx">url</span><span class="p">,</span> <span class="nx">event</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">===</span> <span class="s1">&#39;/assets/ding.mp3&#39;</span><span class="p">,</span>
  <span class="nx">workbox</span><span class="p">.</span><span class="nx">strategies</span><span class="p">.</span><span class="nx">cacheFirst</span><span class="p">()</span>
<span class="p">);</span>
</code></pre></div><p>Finally, it was important to get this to work without browser chrome, and launch in landscape mode. The <a href="https://developers.google.com/web/fundamentals/web-app-manifest">web app manifest</a> gives developers control over both of these things with the <code>display</code> and <code>orientation</code> properties.</p>
<div class="highlight"><pre class="chroma"><code class="language-json" data-lang="json"><span class="p">{</span>
  <span class="nt">&#34;display&#34;</span><span class="p">:</span> <span class="s2">&#34;standalone&#34;</span><span class="p">,</span>
  <span class="nt">&#34;orientation&#34;</span><span class="p">:</span> <span class="s2">&#34;landscape&#34;</span>
<span class="p">}</span>
</code></pre></div><p>Once deployed to Netlify, part one was complete!</p>
<h2 id="accelerometer-access">Accelerometer access</h2>
<p>Accessing device hardware sensors comes with some challenges. For one: security. In days of yore, you could gain access to this sensor without any user permission. This had <a href="https://arxiv.org/abs/1602.04115">massive security implications</a>, namely that malicious code could infer PIN entries based on the angle of the device. Yikes.</p>
<p>This has quite rightly led to a big clampdown on accessing the API. Unfortunately, this means there&rsquo;s a lot of conflicting documentation out there, making it hard to learn about the accelerometer today.</p>
<p>Additionally, hardware API&rsquo;s spit out a <strong>huge</strong> number of events, and sifting through them can be a challenge. More on that in a bit.</p>
<h3 id="deviceorientation-vs-accelerometer">DeviceOrientation vs. Accelerometer</h3>
<p>I started off looking into the <code>Accelerometer</code> API. On first glance, it was perfect:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">accelerometer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Accelerometer</span><span class="p">({</span> <span class="nx">frequency</span><span class="o">:</span> <span class="mi">30</span> <span class="p">});</span>

<span class="nx">accelerometer</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;reading&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="p">{</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">z</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">accelerometer</span><span class="p">;</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">z</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">accelerometer</span><span class="p">.</span><span class="nx">start</span><span class="p">();</span>
</code></pre></div><p>But <a href="https://caniuse.com/#feat=accelerometer">poor browser support</a> makes this a no-go for now. Furthermore, it&rsquo;s wrapped up in the deeply confusing, and poorly supported <code>Permission</code>s API. This wasn&rsquo;t going to work.</p>
<p>I looked into <code>DeviceOrientation</code>, an event on the <code>window</code> that emits the gyroscope position of the phone. But I initially struggled to get this hooked up. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation">MDN</a> example seemed to work on my phone, but although the same simple example in my codebase and local server didn&rsquo;t error, it also didn&rsquo;t output any events.</p>
<p>A library called <a href="https://github.com/dorukeker/gyronorm.js">Gyronorm</a> was linked to from the very same article, but that had all sorts of complications with linked third-party modules not playing ball. It was beginning to look like this wasn&rsquo;t going to be possible.</p>
<p>I went back to the <code>DeviceOrientation</code> code and deployed it to Netlify. Loading that version DID work! The earlier issue was down to a lack of SSL on the local server. Progress!</p>
<h3 id="picking-events">Picking events</h3>
<p>Wading through the <code>deviceorientation</code> events was a case of trial and error.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;deviceorientation&#39;</span><span class="p">,</span> <span class="nx">e</span> <span class="p">=&gt;</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">alpha</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">beta</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">gamma</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div><p>After deploying and running this, I held up my phone and started moving it around to get a feel of which value I should be focusing on. This game only requires the tracking of one angle: facing the phone downwards, and it appeared to be <strong>Gamma</strong>.</p>
<p>I wrote up some code to handle the movement and triggering of the next question:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="c1">// Convert all negative to positive integers
</span><span class="c1"></span><span class="kr">const</span> <span class="nx">angle</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">gamma</span><span class="p">);</span>
<span class="c1">// See if the angle is within our bounds of accuracy
</span><span class="c1"></span><span class="kr">const</span> <span class="nx">angleMatches</span> <span class="o">=</span> <span class="nx">angle</span> <span class="o">&lt;</span> <span class="nx">DEGREES</span><span class="p">;</span>
<span class="kr">const</span> <span class="p">{</span> <span class="nx">nexting</span><span class="p">,</span> <span class="nx">gameState</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">;</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">angleMatches</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">nexting</span> <span class="o">&amp;&amp;</span> <span class="nx">gameState</span> <span class="o">===</span> <span class="s1">&#39;running&#39;</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Add the catch, call the next question method
</span><span class="c1"></span>  <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">nexting</span><span class="o">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="k">this</span><span class="p">.</span><span class="nx">next</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">angleMatches</span> <span class="o">&amp;&amp;</span> <span class="nx">nexting</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Remove the catch
</span><span class="c1"></span>  <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">nexting</span><span class="o">:</span> <span class="kc">false</span> <span class="p">});</span>
<span class="p">}</span>
</code></pre></div><p>Apologies for the word <code>nexting</code>, the API throws out an awful lot of events, so it&rsquo;s important to put a catch in place to stop the next question from being called multiple times before the user has lifted the phone back up to their head.</p>
<h3 id="debugging">Debugging</h3>
<p>I gave this a spin on my phone and iPad, and to my great surprise, it worked! Excited by this, I sent it to a friend with an iPhone. Surprise, surprise, yet more problems. The accelerometer did nothing.</p>
<p>I pondered for a while, and sent the site over to <a href="https://adactio.com/">Jeremy</a>. It also didn&rsquo;t work on his phone. Our original theory was that the <code>deviceorientation</code> API hadn&rsquo;t landed in time for his phone. But after checking the iOS versions of our respective devices, it became apparent it was the opposite problem. iOS 13 disabled the use of the API without a user opt-in.</p>
<p>After several minutes of Googling, I came across <a href="https://github.com/aframevr/aframe/issues/4287">this issue</a> on the Aframe repository. They had also had to overcome this problem to get VR to work in the browser. I delved into <a href="https://github.com/aframevr/aframe/pull/4303/files">the PR</a> fixing this issue, and updated my code to suit:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">requestAccelerometer</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span>
    <span class="k">typeof</span> <span class="nx">DeviceOrientationEvent</span> <span class="o">!==</span> <span class="s1">&#39;undefined&#39;</span> <span class="o">&amp;&amp;</span>
    <span class="k">typeof</span> <span class="nx">DeviceOrientationEvent</span><span class="p">.</span><span class="nx">requestPermission</span> <span class="o">===</span> <span class="s1">&#39;function&#39;</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="nx">DeviceOrientationEvent</span><span class="p">.</span><span class="nx">requestPermission</span><span class="p">()</span>
      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span> <span class="p">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">response</span> <span class="o">===</span> <span class="s1">&#39;granted&#39;</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">this</span><span class="p">.</span><span class="nx">startAccelerometer</span><span class="p">();</span>
        <span class="p">}</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">startAccelerometer</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="nx">startAccelerometer</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;deviceorientation&#39;</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">move</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div><p>The <code>DeviceOrientationEvent.requestPermission()</code> method triggers a browser-based on-screen prompt for the user, asking if they are happy to use the accelerometer on this site. If they approve, we can start listening for those events.</p>
<p>With all that pieced together, hundreds of ideas collated, and a quick lick of visual paint, the site was ready to go! A good morning&rsquo;s &lsquo;work&rsquo;! Below is a video of me playing the game, and here&rsquo;s a link to <a href="http://trys-heads-up.netlify.com/">the live game</a>.</p>
<div style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;">
  <video src="https://www.trysmudford.com/images/blog/heads-up.mp4" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" preload="none" controls></video>
</div>
]]>
      </description>
    </item>
    
    <item>
      <title>Basil: Secret Santa as a Service</title>
      <link>https://www.trysmudford.com/blog/basil/</link>
      <pubDate>Mon, 09 Dec 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/basil/</guid>
      <description><![CDATA[
<p>I recently launched <a href="https://basil.christmas.trysmudford.com/">basil.christmas.trysmudford.com</a>, a &lsquo;Secret Santa as a service&rsquo;. Basil has two modes. The first is a traditional list generator, letting you shuffle the participants and print off a nice, foldable list of names. The second is a little more involved, but considerably more exciting! A &lsquo;head elf&rsquo; from your company can sign up and enter all participant names into the system. Basil will email everyone, letting them know who their giftee is.</p>
<p>But that&rsquo;s only the beginning. We&rsquo;ve all been there; you start in a new company and you get assigned the one person you&rsquo;ve never spoken to. This is where Basil steps in. In each email, there&rsquo;s a unique link that, when clicked, anonymously emails the giftee, asking for a little nudge in the right direction. They can respond, all without knowing who has asked for help! Basil-based encryption!</p>
<p><img src="/images/blog/basil-screenshot.jpg" alt="Basil.christmas home page"></p>
<p>The legend of &lsquo;Basil&rsquo; started many moons ago at Clearleft, when <a href="https://twitter.com/qwertykate">Kate Bulpitt</a> discovered the mysterious elvish hero. <del>Kate</del> *ahem* Basil organised the whole affair, emailing each person in the team; acting as the encryption go-between. This worked great, but meant that individual knew all the Secret Santarers. It sounded to me like an opportunity for tech!</p>
<p>To be honest, it&rsquo;s a bit of a silly side project, but there&rsquo;s always ample opportunity for learning on websites like this. With no client restraints or budgets to consider, a side project is a great opportunity to try out new technologies and push ones design chops.</p>
<h2 id="the-stack">The stack</h2>
<p><strong>Vue.js</strong> is my go-to framework. When set up with <strong>Nuxt.js</strong>, I find it really quick and empowering to build in. Rather than spin up a Node.js server, I opted to use the <code>generate</code> mode, and host the site on <strong>Netlify</strong>. It gives me CDN hosting and a great devops experience with zero configuration.</p>
<p>The database &amp; backend layer was an interesting choice, and where I focused my learning efforts. I opted for an avant-garde option of <strong>Airtable</strong> + <strong>Netlify Functions</strong>. Airtable is a lovechild of a spreadsheet and a database. The columns are typed, and the rows can be linked, so it&rsquo;s possible to run it as a relational database. It has a very sensible API (and incredible live API docs). I used Airtable for our <a href="https://www.trysmudford.com/blog/rapid-building/">signature generator</a>, but rather than use the HTTP REST API, this time I went for the <code>npm</code> module. Another learning opportunity.</p>
<p>The module still uses callbacks, so there was a bit of &lsquo;promisification&rsquo; required to get it work nicely with <code>async/await</code>.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">exports</span><span class="p">.</span><span class="nx">updateElf</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rowId</span><span class="p">,</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">base</span><span class="p">(</span><span class="s1">&#39;Elves&#39;</span><span class="p">).</span><span class="nx">update</span><span class="p">(</span><span class="nx">rowId</span><span class="p">,</span> <span class="nx">payload</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">err</span> <span class="o">?</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">Errors</span><span class="p">.</span><span class="nx">Generic</span><span class="p">)</span> <span class="o">:</span> <span class="nx">resolve</span><span class="p">();</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">};</span>
</code></pre></div><p>Netlify Functions are &lsquo;serverless lambda functions&rsquo; that run as individual backends. When called, they spin up and make calls to the Airtable database. All API authentication is handled with environment variables stored on Netlify, so when developed with <a href="https://www.netlify.com/products/dev/">Netlify Dev</a>, all your security is handled for you.</p>
<p>Emails were handled by <strong>Mailgun</strong>. The biggest hurdle was getting the first email to send. Their documentation doesn&rsquo;t currently mention a different API URL for EU domains. As soon as I found <a href="https://stackoverflow.com/a/52562241/2233707">this stackoverflow answer</a>, I was away.</p>
<h2 id="classic-form-posts">Classic form POSTs</h2>
<p>Rather than use AJAX and JSON requests as is the usual approach in this decade, I ended up going old school and use form POSTs and redirects for the data exchange. This meant I could start from a base of solid HTML, without worrying about requests from JavaScript. It might not be quite as seamless having full page refreshes, but given most users will only see one form in the whole flow, it doesn&rsquo;t harm the experience.</p>
<p>Deciding when to add complexity, and when to hold back, is another skill that&rsquo;s worth honing. So regularly do we reach for the shiny tool, when the slightly dusty one will do just fine. Even in modern tools, like Vue.js, there&rsquo;s still plenty of power in the humble <code>&lt;form&gt;</code> and 302 redirect.</p>
<h2 id="design">Design</h2>
<blockquote>
<p>I&rsquo;m not a designer, but I do love Christmas.</p>
</blockquote>
<p>With those credentials out the way, I decided to have a crack at designing this site. <a href="https://creativemarket.com/">Creative Market</a> was my biggest friend on this project. There are so many über talented individuals on that platform. As soon as I stumbled upon these creatures, I fell in love.</p>
<p><img src="/images/blog/characters.jpg" alt="A selection of the Basil forest characters"></p>
<p>The colour scheme and typography had to be suitably festive for such a project. <strong>Zeichen</strong>, and <strong>DM Sans</strong> provided a nice mix of conversational &lsquo;basil tone&rsquo; and readable prose. A contrasting scheme of pink and dark blue, combined with lashings of noise, and topped with some wonderful snowflakes (created by Cassie), let to a suitably seasonable creation.</p>
<p><img src="/images/blog/scheme.jpg" alt="The pink and blue colour scheme"></p>
<p>The initial design direction was decided in Sketch, but I quickly moved to the browser to roll it out. Working in Vue.js components, I was able to swiftly build out the various form-based pages with great ease.</p>
<h2 id="error-codes">Error codes</h2>
<p>Deciding on an error structure is easily overlooked. As this project used redirects, rather than <code>JSON</code> responses, it was important to establish a format that could be interpreted by the frontend.</p>
<p>I came up with an &lsquo;enum&rsquo; of possible error codes and shared it between the front- and backend. If the serverless function ever caught an error, it redirected the user to <code>/error/${ErrorCode}/</code> where Nuxt rendered the appropriate message to the user. This was also an opportunity to play around with Basil&rsquo;s tone of voice.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="p">{</span>
  <span class="nx">TokenExpired</span><span class="o">:</span> <span class="s1">&#39;My dearest elf, it\&#39;s time to log in again&#39;</span><span class="p">,</span>
  <span class="nx">AlreadyContacted</span><span class="o">:</span> <span class="s1">&#39;Have patience, child. You\&#39;ve already contacted that elf!&#39;</span><span class="p">,</span>
  <span class="nx">FarTooMany</span><span class="o">:</span> <span class="s1">&#39;Humble apologies, you\&#39;ve hit your elvish quota!&#39;</span>
<span class="p">}</span>
</code></pre></div><h2 id="planning-for-appropriate-scale">Planning for &lsquo;appropriate scale&rsquo;</h2>
<p>Basil was never going to take over the internet, but there was a chance a few others may like to use it. Rather than build it just for our internal use at <a href="https://clearleft.com/">Clearleft</a>, I made sure the schema was set up to allow multiple groups to use the system. This did mean the added complication of putting in an authentication system, but that in itself was an opportunity to build a password-less authentication flow for the first time.</p>
<p>There was no need to <a href="https://twitter.com/dhh/status/1201992702860107776">prepare for greater scale</a> than that. I think we&rsquo;ve all been burned in the past worrying about whether the stack will cope with X users, with no basis for whether <em>any</em> users will arrive. More and more, I&rsquo;m realising that building for myself, keeping in mind not to be exclusionary, nor paint myself into a corner, is the best bet for web things.</p>
<h2 id="give-it-a-go">Give it a go!</h2>
<p>If you&rsquo;re in the market for a Secret Santa generator, managed or otherwise, please feel free to give Basil a whirl!</p>
<a href="https://basil.christmas.trysmudford.com" class="button">Visit Basil</a>

]]>
      </description>
    </item>
    
    <item>
      <title>Tiny lesson: rapid builds, email signatures and Airtable</title>
      <link>https://www.trysmudford.com/blog/rapid-building/</link>
      <pubDate>Wed, 28 Aug 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/rapid-building/</guid>
      <description><![CDATA[
<p>We&rsquo;re fortunate enough to have some rather snazzy email signatures, kindly created by <a href="https://clearleft.com/about/team/benjamin-parry">Benjamin</a>. He&rsquo;s been lovingly crafting these by hand; diligently updating them each time an event concludes or a new Clearleftie joins. This seemed like a fun and helpful task to automate. After a morning of hackery, I had a working version of the <a href="https://clearleft-signatures.netlify.com/">signature generator</a> deployed and ready for an internal test.</p>
<div style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;">
  <video src="https://www.trysmudford.com/images/blog/signature.mp4" poster="https://www.trysmudford.com/images/blog/signature.jpg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" preload="none" controls></video>
</div>
<hr>
<p>There are a couple of interesting technical decisions which we&rsquo;ll delve into, but before that, let&rsquo;s talk about building at pace more generally.</p>
<h2 id="quick-web-things-quick-web-wins">Quick web things, quick web wins</h2>
<p>One of my passions is rapidly testing ideas on the web. Development pace is one of the biggest things the web has going for it over native applications. A HTML file, domain, server space and a couple of hours is all you need to get <em>something</em> online.</p>
<p>It&rsquo;s worth saying upfront that rapid builds are <strong>definitely not</strong> for everything. In fact, they should be used sparingly for prototypes or side projects that aren&rsquo;t business-critical. Like the <a href="https://fastgood.cheap/">good, fast, cheap</a> venn diagram, quick builds are inherently flawed, but they do still have merit.</p>
<p>I really love a thorough plan and technical spec, but there&rsquo;s something wonderful about rapidly spiking a problem and not worrying too much about the details. It&rsquo;s how <a href="https://sergey.trysmudford.com">Sergey</a>, <a href="https://pedalboard.netlify.app/">JS Pedalboard</a>, <a href="https://javasnack.cool/">Javasnack</a> and several marginally popular <a href="https://wtf1.com/post/someones-made-a-has-mclaren-broken-down-today-website/">F1 parody websites</a> came about.</p>
<blockquote>
<p>The stack choice isn&rsquo;t really important.</p>
</blockquote>
<p>Spikes are a great way to try a new technology and learn by making mistakes. The only pre-requisite I&rsquo;d suggest is to <strong>use a stack that you can deploy easily</strong>. There&rsquo;s nothing worse than getting something working locally, then finding you can&rsquo;t host it without an AWS degree or pricey hosting infrastructure.</p>
<p>In the past, PHP was my jam. It&rsquo;s still <em>so</em> much easier to host than Node.js, and you can build quickly without getting too bogged down in implementation details. These days I&rsquo;m tending to lean on static site generators like <a href="https://gohugo.io">Hugo</a> and <a href="https://sergey.trysmudford.com">Sergey</a> (shameless plug), before deploying to Netlify.</p>
<p>For this project, I opted for <a href="https://preactjs.com/">Preact</a> and a small vanilla Node build script. <a href="https://github.com/preactjs/preact-cli">Preact CLI</a> boilerplated the site very quickly, and after a few minutes of stripping back the extra cruft, I had an ES6, reactive and hot-loaded development environment ready. I then ran a quick &lsquo;hello, world&rsquo; deploy to confirm it would all build on Netlify.</p>
<p>With that in place, it was time to actually build the darn thing.</p>
<p>From the <a href="/blog/rapid-building/#come-up-with-a-bit-of-a-plan">very rough plan</a> in my head, it appeared there were two main parts to this project:</p>
<ol>
<li>The template generator - Preact</li>
<li>The data source - JSON &amp; Airtable</li>
</ol>
<h2 id="generating-code-with-code">Generating code with code</h2>
<p>Email signatures are notoriously awful to code, but fortunately Benjamin had done the hard work already! I grabbed an existing signature and converted it into a little method that squirted the parts of a &lsquo;person&rsquo; in, mixed it with some sensible defaults, and returned some HTML (well, JSX).</p>
<div class="highlight"><pre class="chroma"><code class="language-jsx" data-lang="jsx"><span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">forename</span><span class="o">:</span> <span class="s1">&#39;Trys&#39;</span><span class="p">,</span>
  <span class="nx">surname</span><span class="o">:</span> <span class="s1">&#39;Mudford&#39;</span><span class="p">,</span>
  <span class="nx">team_name</span><span class="o">:</span> <span class="s1">&#39;trys-mudford&#39;</span><span class="p">,</span>
  <span class="nx">avatar_name</span><span class="o">:</span> <span class="s1">&#39;trys-mudford-small&#39;</span><span class="p">,</span>
  <span class="nx">role</span><span class="o">:</span> <span class="s1">&#39;Front end developer&#39;</span>
<span class="p">};</span>

<span class="nx">renderSignature</span> <span class="o">=</span> <span class="nx">person</span> <span class="p">=&gt;</span> <span class="p">(</span>
  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;min-height:50px;line-height:17px;color:#505050;min-width:350px;font-family: Arial, sans-serif; font-size: 10pt; line-height: 1.5;&#34;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{</span><span class="nx">person</span><span class="p">.</span><span class="nx">forename</span><span class="p">}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="o">---</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">defaults</span><span class="p">.</span><span class="nx">team_url</span> <span class="o">+</span> <span class="nx">person</span><span class="p">.</span><span class="nx">team_name</span><span class="p">}&gt;</span>
      <span class="p">&lt;</span><span class="nt">img</span>
        <span class="na">style</span><span class="o">=</span><span class="s">&#34;float:left;margin:2px 6px 32px 0;width:90px&#34;</span>
        <span class="na">src</span><span class="o">=</span><span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">defaults</span><span class="p">.</span><span class="nx">avatar_url</span> <span class="o">+</span> <span class="nx">person</span><span class="p">.</span><span class="nx">avatar_name</span> <span class="o">+</span> <span class="s1">&#39;.png&#39;</span><span class="p">}</span>
        <span class="na">alt</span><span class="o">=</span><span class="p">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">forename</span> <span class="o">+</span> <span class="s1">&#39; Profile Pic&#39;</span><span class="p">}</span>
      <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">strong</span><span class="p">&gt;{</span><span class="nx">person</span><span class="p">.</span><span class="nx">forename</span> <span class="o">+</span> <span class="s1">&#39; &#39;</span> <span class="o">+</span> <span class="nx">person</span><span class="p">.</span><span class="nx">surname</span><span class="p">}&lt;/</span><span class="nt">strong</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">br</span> <span class="p">/&gt;</span>
      <span class="p">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">role</span><span class="p">}</span> <span class="o">|</span><span class="p">{</span><span class="s1">&#39; &#39;</span><span class="p">}</span>
      <span class="p">&lt;</span><span class="nt">a</span>
        <span class="na">style</span><span class="o">=</span><span class="s">&#34;color:#006ff5;text-decoration:none;font-weight:700;border-bottom:1px&#34;</span>
        <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://clearleft.com/&#34;</span>
      <span class="p">&gt;</span>
        <span class="nx">Clearleft</span>
      <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">br</span> <span class="p">/&gt;</span>
      <span class="p">&lt;</span><span class="nt">a</span>
        <span class="na">style</span><span class="o">=</span><span class="s">&#34;color:#505050;text-decoration:none&#34;</span>
        <span class="na">href</span><span class="o">=</span><span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">defaults</span><span class="p">.</span><span class="nx">phone_url</span><span class="p">}</span>
      <span class="p">&gt;</span>
        <span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">defaults</span><span class="p">.</span><span class="nx">phone_text</span><span class="p">}</span>
      <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
<span class="p">);</span>
</code></pre></div><p>The next step was to import the list of staff from a JSON file, pick out a selected team member and render the above template. Thanks to JS imports, this was nice and clean to achieve:</p>
<div class="highlight"><pre class="chroma"><code class="language-jsx" data-lang="jsx"><span class="kr">import</span> <span class="p">{</span> <span class="nx">h</span><span class="p">,</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;preact&#39;</span><span class="p">;</span>
<span class="kr">import</span> <span class="nx">data</span> <span class="nx">from</span> <span class="s1">&#39;./data&#39;</span><span class="p">;</span>

<span class="kr">class</span> <span class="nx">App</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
    <span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">team</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">x</span> <span class="p">=&gt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">team_name</span> <span class="o">===</span> <span class="s1">&#39;trys-mudford&#39;</span><span class="p">);</span>

    <span class="k">return</span> <span class="p">(</span>
      <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="p">{</span><span class="nx">person</span> <span class="o">&amp;&amp;</span> <span class="p">(</span>
          <span class="p">&lt;</span><span class="nt">section</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;person&#34;</span><span class="p">&gt;{</span><span class="k">this</span><span class="p">.</span><span class="nx">renderSignature</span><span class="p">(</span><span class="nx">person</span><span class="p">)}&lt;/</span><span class="nt">section</span><span class="p">&gt;</span>
        <span class="p">)}</span>
      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>Next I moved the hard-coded user identifier up into state, and added a <code>&lt;select&gt;</code> field to control it.</p>
<div class="highlight"><pre class="chroma"><code class="language-jsx" data-lang="jsx"><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">teamName</span><span class="o">:</span> <span class="s1">&#39;&#39;</span>
<span class="p">};</span>

<span class="nx">setTeamName</span> <span class="o">=</span> <span class="nx">event</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">teamName</span><span class="o">:</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span> <span class="p">});</span>
<span class="p">};</span>

<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;who&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;screen-reader-only&#34;</span><span class="p">&gt;</span>
        <span class="nx">Pick</span> <span class="nx">a</span> <span class="nx">team</span> <span class="nx">member</span>
      <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">select</span>
        <span class="na">id</span><span class="o">=</span><span class="s">&#34;who&#34;</span>
        <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">teamName</span><span class="p">}</span>
        <span class="na">onChange</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setTeamName</span><span class="p">}</span>
      <span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">option</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;&#34;</span><span class="p">&gt;</span><span class="nx">Who</span> <span class="nx">are</span> <span class="nx">you</span><span class="o">?</span><span class="p">&lt;/</span><span class="nt">option</span><span class="p">&gt;</span>
        <span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">team</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">person</span> <span class="p">=&gt;</span> <span class="p">(</span>
          <span class="p">&lt;</span><span class="nt">option</span> <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">team_name</span><span class="p">}&gt;</span>
            <span class="p">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">forename</span><span class="p">}</span> <span class="p">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">surname</span><span class="p">}</span>
          <span class="p">&lt;/</span><span class="nt">option</span><span class="p">&gt;</span>
        <span class="p">))}</span>
      <span class="p">&lt;/</span><span class="nt">select</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
  <span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>Finally, I added a touch of state restoration with the help of <code>localStorage</code>. When a user returns to the site for a second time, their previous staff choice gets prefilled, saving one click. The goal of this site is to save us time so this feature is; although by no means essential, surprisingly useful.</p>
<div class="highlight"><pre class="chroma"><code class="language-jsx" data-lang="jsx"><span class="kr">const</span> <span class="nx">STORAGE_NAME</span> <span class="o">=</span> <span class="s1">&#39;signatureTeamName&#39;</span><span class="p">;</span>

<span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">teamName</span><span class="o">:</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="nx">STORAGE_NAME</span><span class="p">)</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span>
<span class="p">};</span>

<span class="nx">setTeamName</span> <span class="o">=</span> <span class="nx">event</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">teamName</span><span class="o">:</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span> <span class="p">},</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="nx">STORAGE_NAME</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">teamName</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">};</span>
</code></pre></div><p>With that, plus a bit of styling, the frontend was complete.</p>
<h2 id="airtable-api">Airtable API</h2>
<p>The above was achieved with a static JSON file, which was super rapid to build with. As an MVP, this all works and could genuinely be used in production - there&rsquo;s no shame in avoiding databases altogether. But part of the fun in rapid building is trying new things out.</p>
<p>Airtable is like Excel on steroids - and a spreadsheet seemed like the most straightforward way to get data into this system without getting tied up in databases and servers. I considered Google Sheets, but their API authentication was too cumbersome, so Airtable won the day. As I said, pick tools that deploy easily!</p>
<p>Once I had an API key, I created a file called <code>fetch.js</code> and ran <code>node fetch.js</code> in the terminal. This runs whatever JS is in the file - like a Bash script for those of us who don&rsquo;t know Bash. Data fetching in Node is still less than ideal, but I&rsquo;ve got a handy little method that converts the in built <code>https</code> library into a promise:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">https</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;https&#39;</span><span class="p">);</span>

<span class="cm">/**
</span><span class="cm"> * Generic HTTP Get request promisified
</span><span class="cm"> * @param {string} url - the API endpoint
</span><span class="cm"> * @returns {Promise&lt;Object&gt;} - the response
</span><span class="cm"> */</span>
<span class="kd">function</span> <span class="nx">get</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">https</span>
      <span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">res</span> <span class="p">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
        <span class="nx">res</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;data&#39;</span><span class="p">,</span> <span class="nx">chunk</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="nx">data</span> <span class="o">+=</span> <span class="nx">chunk</span><span class="p">));</span>
        <span class="nx">res</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;end&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">)));</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=&gt;</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">));</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div><p>The response from Airtable is an object with a <code>records</code> array. Each record is a row in the spreadsheet which in turn has a <code>fields</code> property. Each item in this object is keyed to the name of the column, and represents a cell.</p>
<p>I started out writing some fairly <em>dodgy but working™</em> code to take the rows, loop them and add them to a new array. That array was then converted into a new JSON file ready to be consumed by the Preact application. Once I&rsquo;d confirmed that was all working, I refactored a bit and ended up with this:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
</span><span class="cm"> * Parse Airtable response, running through a transform callback function
</span><span class="cm"> * @param {string} url - the Airtable endpoint
</span><span class="cm"> * @param {transform} transform - the transform function to run through
</span><span class="cm"> * @returns {Promise&lt;AirtableRecord[]&gt;} - an array of records
</span><span class="cm"> */</span>
<span class="kd">function</span> <span class="nx">fetchFromAirTable</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">transform</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">get</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">res</span> <span class="p">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">records</span>
      <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">x</span> <span class="p">=&gt;</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">x</span><span class="p">.</span><span class="nx">fields</span><span class="p">).</span><span class="nx">length</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">transform</span><span class="p">)</span>
    <span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">fetchStaff</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">fetchFromAirTable</span><span class="p">(</span>
    <span class="sb">`https://api.airtable.com/v0/</span><span class="si">${</span><span class="nx">SPREADSHEET</span><span class="si">}</span><span class="sb">/Staff?maxRecords=40&amp;view=Grid%20view&amp;api_key=</span><span class="si">${</span><span class="nx">KEY</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span>
    <span class="nx">record</span> <span class="p">=&gt;</span> <span class="p">({</span>
      <span class="nx">forename</span><span class="o">:</span> <span class="nx">record</span><span class="p">.</span><span class="nx">fields</span><span class="p">[</span><span class="s1">&#39;First Name&#39;</span><span class="p">],</span>
      <span class="nx">surname</span><span class="o">:</span> <span class="nx">record</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">Surname</span><span class="p">,</span>
      <span class="nx">team_name</span><span class="o">:</span> <span class="nx">record</span><span class="p">.</span><span class="nx">fields</span><span class="p">[</span><span class="s1">&#39;Team Name&#39;</span><span class="p">],</span>
      <span class="nx">avatar_name</span><span class="o">:</span> <span class="nx">record</span><span class="p">.</span><span class="nx">fields</span><span class="p">[</span><span class="s1">&#39;Avatar Name&#39;</span><span class="p">],</span>
      <span class="nx">role</span><span class="o">:</span> <span class="nx">record</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">Role</span>
    <span class="p">})</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Fetching data from Airtable...&#39;</span><span class="p">);</span>

  <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span><span class="nx">fetchStaff</span><span class="p">()])</span>
    <span class="p">.</span><span class="nx">then</span><span class="p">(([</span><span class="nx">team</span><span class="p">])</span> <span class="p">=&gt;</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
        <span class="nx">team</span><span class="p">,</span>
        <span class="nx">defaults</span>
      <span class="p">});</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Writing results...&#39;</span><span class="p">);</span>
      <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s1">&#39;src/data/index.json&#39;</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Build complete&#39;</span><span class="p">);</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">err</span> <span class="p">=&gt;</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;Fetching failed&#39;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
    <span class="p">});</span>
<span class="p">})();</span>
</code></pre></div><p>Instead of pushing the transformed data into a new array, I relied on the wonderful Array methods we have available in JS. Using <code>.map()</code> with a callback worked as a really neat way to extract the data transformation out into the calling function, simultaneously keeping the data fetching code nice and generic.</p>
<p>Scaling this to work with &lsquo;events&rsquo; as well as &lsquo;staff&rsquo; was a case of creating a new method, adding the appropriate API URL, and writing a new transform function.</p>
<h2 id="build-time-fetching">Build time fetching</h2>
<p>One option would&rsquo;ve been to hit the Airtable API directly on the client-side. This has the advantage of always being up to date, but has a few downsides:</p>
<ul>
<li>Additional point of failure on the live site</li>
<li>Dependant on Airtable keeping their API format consistent</li>
<li>Rate limiting &amp; pricing considerations</li>
<li>&lsquo;Dangers&rsquo; of exposing all the spreadsheet data</li>
<li>Definite dangers of exposing API keys</li>
<li>CORS hell</li>
</ul>
<p>The approach I took was to fetch the data once at build time, create a new JSON file and read that in to Preact. It&rsquo;s the same technique I used on the 2018 incarnation of <a href="https://www.tomango.co.uk/thinks/paul-the-octopus-2018/#build-time-api">Paul the Octopus</a>.</p>
<p>The biggest benefit of this approach is <a href="https://adactio.com/articles/12839#howwelldoesitfail">how well it fails</a>. If: Airtable change their API design/auth, someone updates the spreadsheet format drastically, or the sky falls in, new releases will simply not build and the current release will continue to stay live. I&rsquo;ll get an email alerting me to the failed build, and I can investigate in my own time.</p>
<h2 id="hiding-secrets">Hiding secrets</h2>
<p>With any quick build, you need to decide what&rsquo;s worth optimising and what&rsquo;ll &lsquo;do&rsquo; for the MVP. If you get bogged down optimising prematurely, you&rsquo;ll never ship anything. If you cut too many corners, the product will be unsalvageable. The trick is to avoid painting oneself into a corner.</p>
<p>Environment variables are one of those things that are worth setting up early doors. They&rsquo;re not exactly exciting, but retrospectively adding them is even less fun. Plus, the very act of adding them to a project forces you to consider how the site will be deployed.</p>
<p>Hard coding secrets into a repository isn&rsquo;t a hugely clever idea, so it&rsquo;s good practice to create an <code>.env</code> file, pull in the <a href="https://www.npmjs.com/package/dotenv">dotenv</a> module, and rely on environment variables from the start.</p>
<h2 id="come-up-with-a-bit-of-a-plan">Come up with a (bit of a) plan</h2>
<p>You don&rsquo;t have to fly totally blind with projects like this. It&rsquo;s worth coming up with a small plan, even if it&rsquo;s only in your head. For this project, the plan looked a bit like:</p>
<ul>
<li>Decide on a stack</li>
<li>Bootstrap the site</li>
<li>Render a plain HTML signature</li>
<li>Make a template to render a signature from an object</li>
<li>Extract user details &amp; defaults into a JSON file</li>
<li>Fetch something from Airtable</li>
<li>Save that thing as JSON</li>
<li>Trigger the fetch at build time</li>
</ul>
<p>If you have a reasonably big idea in mind, it&rsquo;s worth breaking it down into smaller features first. This &lsquo;backlog prioritisation&rsquo; exercise might sound pretty formal for a single day build, but I find it helps me stay focused. I quite like GitHub projects &amp; Trello for this task - I&rsquo;ll make &lsquo;MVP, nice to have, backlog, in progress, done&rsquo; columns and divide the features accordingly. The <a href="https://en.wikipedia.org/wiki/MoSCoW_method">MoSCoW method</a> is a decent alternative approach.</p>
<h2 id="guessed-requirements">Guessed requirements</h2>
<p>The final thing I wanted to touch on was guessed requirements. It&rsquo;s an inevitability that the thing you build will have some rough edges and won&rsquo;t work perfectly first time out. But that&rsquo;s okay, you&rsquo;re not building a business-critical system, you&rsquo;re building a ✨ <em>fun web thing</em> ✨</p>
<p>With this project, I got a bit carried away and added a &lsquo;copy the code&rsquo; feature. It ran the <code>renderSignature</code> method through Preact&rsquo;s <a href="https://github.com/preactjs/preact-render-to-string">render to string</a> library, before copying it to your clipboard with <code>execCommand</code>. There were some interesting success/error states to consider and I had to use <code>refs</code> to select DOM nodes within the application.</p>
<p>The only problem was, the feature wasn&rsquo;t needed.</p>
<p>Gmail and Apple mail both work from the default browser selection and clipboard, and don&rsquo;t allow you to paste HTML. So the feature was swiftly removed. It could&rsquo;ve been avoided with some basic specifications, but it also wasn&rsquo;t a big deal. The feature took about 30 minutes to add, and was a nice problem to solve.</p>
<p>The fact that it didn&rsquo;t make it to launch matters little, it was still useful to learn and code, even if I was the only beneficiary.</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Making a loop pedal with MediaRecorder</title>
      <link>https://www.trysmudford.com/blog/media-recorder-loop-pedal/</link>
      <pubDate>Mon, 13 May 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/media-recorder-loop-pedal/</guid>
      <description><![CDATA[
<p>After building the <a href="/blog/pedalboard/">JS pedalboard</a>, I ended the launch post with an ambitious stretch goal:</p>
<blockquote>
<p>A loop pedal would also be incredible.<br>
But who knows.</p>
</blockquote>
<p>After a Saturday of hacking, I&rsquo;m pleased to say the loop pedal is alive and working! It&rsquo;s all based around the fantastic, but relatively unknown <code>MediaRecorder</code> API.</p>
<div class="video-embed">
	
		<div style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;">
			<iframe src="//www.youtube-nocookie.com/embed/A-qqC3x6_Bk" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" allowfullscreen="1" frameborder="0" title="YouTube Video"></iframe>
		</div>
	
</div>

<a href="https://pedalboard.netlify.app/" class="button">Try the pedalboard out for yourself!</a>

<p>The video also uses the new Smoosh compressor and <code>Math.pow()</code> overdrive.</p>
<hr>
<p>Here&rsquo;s a diagram of the audio routing in the <code>for(loop)</code> pedal:</p>
<p><img src="/images/blog/loop-routing.png" alt="Loop pedal routing diagram, explained below"></p>
<ol>
<li>A loop pedal is an &lsquo;always-on&rsquo; pedal, so the input is passed straight to the output.</li>
<li>It&rsquo;s also sent to the &lsquo;Mix in&rsquo; gain node. This is for summing purposes for overdubbing.</li>
<li><code>MediaRecorder</code> will only accept a &lsquo;stream&rsquo;, so we have to do a little conversion from the gain node with a <code>ctx.createMediaStreamDestination()</code>.</li>
<li>It&rsquo;s then passed to the <code>MediaRecorder</code>.</li>
<li>Once the recording has finished, we pass the audio into an <code>&lt;audio&gt;</code> tag and read the sound back out of there.</li>
<li>The audio is sent back into the &lsquo;Mix in&rsquo; node, to allow us to overdub loops.</li>
<li>Finally, the audio tag is also run into a gain node, to control the output volume.</li>
</ol>
<p>The code looks a little like this:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">mixIn</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">volume</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">streamer</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createMediaStreamDestination</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">recorder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MediaRecorder</span><span class="p">(</span><span class="nx">streamer</span><span class="p">.</span><span class="nx">stream</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">audio</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;audio&#39;</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">audioOut</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createMediaElementSource</span><span class="p">(</span><span class="nx">audio</span><span class="p">);</span>

<span class="nx">input</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">output</span><span class="p">);</span>
<span class="nx">input</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">mixIn</span><span class="p">);</span>
<span class="nx">mixIn</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">streamer</span><span class="p">);</span>
<span class="nx">audioOut</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">volume</span><span class="p">);</span>
<span class="nx">audioOut</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">mixIn</span><span class="p">);</span> <span class="c1">// Loop back around for overdubbing
</span><span class="c1"></span><span class="nx">volume</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">output</span><span class="p">);</span>
</code></pre></div><h2 id="using-the-mediarecorder">Using the <code>MediaRecorder</code></h2>
<p>The <code>MediaRecorder</code> takes a stream and listens to it. When you call <code>recorder.start()</code>, it begins recording, and surprise, surprise, when you call <code>recorder.stop()</code>, it stops! Then, like all browser API&rsquo;s, it fires an event. In this case, it&rsquo;s <code>dataavailable</code> where it passes the audio out. That can be hooked up to an audio tag with a single line:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">onDataAvailable</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">audio</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
  <span class="nx">audio</span><span class="p">.</span><span class="nx">play</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div><p>The initial plan was to use the <code>loop</code> attribute on the audio tag, which worked well till overdubs came into the picture.</p>
<p>As the audio is sent back into the &lsquo;Mix in&rsquo; node, the recording part of overdubbing was surprisingly simple. But it turns out timing the dub is considerably more complicated that recording an initial loop. To simplify things, I opted to only allow overdubs over a full loop length. Once a loop is recorded, clicking the loop button again will prepare the looper for more recording, but will only trigger when the loop comes around again. When it gets to the end, the recording phase ends and we go back to playback mode. In theory, this can be done as many times as you like!</p>
<p>When using the <code>loop</code> attribute, the audio tag never sends an <code>ended</code> event. There is a <code>timeupdate</code> events that appears to be pretty consistent in sending an event at <code>0</code>, but I thought it better to ditch the attribute and listen to the <code>ended</code> event. Then I could immediately call <code>audio.play()</code> again. This didn&rsquo;t appear to make any difference to playback, it&rsquo;s practically instantaneous. I could then hook into the event to start/stop the recorder, and switch the &lsquo;LED&rsquo; colour.</p>
<h2 id="ui">UI</h2>
<p>The other stretch goal was to add some animations to the pedals, and this seemed like the perfect opportunity. I&rsquo;ve mocked the pedal to look like a little cassette deck, complete with playback heads and spinning tape! A bit of <code>border-radius: 100% 95%;</code> and infinite rotation on the tape produced a pretty neat effect.</p>
<p><img src="/images/blog/looper.gif" alt="Emulating a little cassette deck"></p>
<p>In the spirit of &lsquo;always on&rsquo;, there is no on/off switch for this pedal, this did mean re-doing the DOM code for it. I haven&rsquo;t refactored the DOM code just yet though - that might come if another always-on use-case arrives.</p>
<p>MIDI control as a must, as seen by the video. It works in the same way as outlined in the <a href="/blog/pedalboard/#web-midi-api">first post</a>.</p>
<p>And yes, <code>for(loop)</code> was the best name I came up with! Please have a play and let me know what you think!</p>
<a href="https://pedalboard.netlify.app/" class="button">Try the pedalboard out for yourself!</a>

]]>
      </description>
    </item>
    
    <item>
      <title>Building a JavaScript guitar pedalboard</title>
      <link>https://www.trysmudford.com/blog/pedalboard/</link>
      <pubDate>Mon, 06 May 2019 13:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/pedalboard/</guid>
      <description><![CDATA[
<p>I&rsquo;ve just launched a new side project in the form of a <a href="https://pedalboard.netlify.app/">JavaScript guitar pedalboard</a>. It&rsquo;s a handy crossover of my coding and guitaring hobbies.</p>
<div class="video-embed">
	
		<div style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;">
			<iframe src="//www.youtube-nocookie.com/embed/OJVmZ7hbVPQ" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" allowfullscreen="1" frameborder="0" title="YouTube Video"></iframe>
		</div>
	
</div>

<a href="https://pedalboard.netlify.app/" class="button">Try the pedalboard out for yourself!</a>

<hr>
<p>The original intention for the project was to build a delay pedal, but after a Wednesday evening of hackery, I had a working prototype with mix, speed, feedback and tone controls. My mind started to wander, and before long, I had visions of a whole pedalboard!</p>
<p>Each pedal had to have a fun code-y name. For some, it was definitely a case of pun-driven development, where the name decided which pedal to build next. Here&rsquo;s what I came up with:</p>
<ul>
<li><a href="/blog/pedalboard/#delay">Delay</a>: <strong><code>setTimeout</code></strong></li>
<li><a href="/blog/pedalboard/#tremolo">Tremolo</a>: <strong><code>&lt;blink /&gt;</code></strong></li>
<li><a href="/blog/pedalboard/#chorus">Chorus</a>: <strong><code>float</code></strong></li>
<li><a href="/blog/pedalboard/#boost">Boost</a>: <strong><code>!important</code></strong></li>
<li><a href="/blog/pedalboard/#reverb">Reverb</a>: <strong><code>spacer.gif</code></strong></li>
<li><a href="/blog/pedalboard/#wah-wah">Wah Wah</a>: <strong><code>.filter()</code></strong></li>
</ul>
<p><strong>UPDATE</strong><br>
Since this writing post, I&rsquo;ve also added a <strong><code>Smoosh</code></strong> compressor, <strong><code>Math.pow()</code></strong> overdrive and most excitingly, a <strong><code>for(loop)</code></strong> loop pedal! You can read about them <a href="/blog/media-recorder-loop-pedal/">here</a>!</p>
<h2 id="delay">Delay</h2>
<p>Delay was a fun starting point; the Web Audio API has a <a href="https://developer.mozilla.org/en-US/docs/Web/API/DelayNode">DelayNode</a> that takes a stream and delays it be a specified number of seconds. This delays the whole signal, which isn&rsquo;t ideal for a pedal. None of the nodes include a &lsquo;mix&rsquo; control, so you have to built it yourself.</p>
<p>The trick is to split the signal, sending one part straight to the output and the other to into the delay section, starting with a Biquad Filter. This filter is the tone control for the delay repeats, giving the impression of a darker analogue delay, or a crisper digital sound.</p>
<p>Next it heads to the delay node, which does what is says on the tin. Not only does it lack a mix control, it doesn&rsquo;t let you specify the number of repeats. I began thinking about how to tackle it, perhaps with multiple delay nodes. But then I remembered what the &lsquo;number of repeats&rsquo; is usually called: <strong>Feedback!</strong> That means connecting the stream back into itself, or in other words: audio recursion!</p>
<p>The first step is to split the signal again, sending one part to an output <code>GainNode</code> (acting as a mix control), and the other to a feedback <code>GainNode</code>, reducing the volume. This is how we get multiple, controllable repeats of lowering volume. Without the feedback gain stage, the delay would get into a proper feedback loop, creating some crazy sounds.</p>
<p>Then we send it back into the same delay node as before, where it joins the stream and loops back around the nodes. It becomes quieter each loop till it fades out completely.</p>
<p>Here&rsquo;s a diagram of the pedal:</p>
<p><img src="/images/blog/flowchart.jpg" alt="Delay flowchart"></p>
<p>And here&rsquo;s the code:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">filter</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createBiquadFilter</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">delay</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createDelay</span><span class="p">(</span><span class="nx">defaults</span><span class="p">.</span><span class="nx">maxDelay</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">feedback</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">delayGain</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>

<span class="nx">input</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">output</span><span class="p">);</span>
<span class="nx">input</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
<span class="nx">filter</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">delay</span><span class="p">);</span>
<span class="nx">delay</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">feedback</span><span class="p">);</span>
<span class="nx">feedback</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">delay</span><span class="p">);</span>
<span class="nx">feedback</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">delayGain</span><span class="p">);</span>
<span class="nx">delayGain</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">output</span><span class="p">);</span>
</code></pre></div><p>It&rsquo;s all very &ldquo;<a href="https://www.youtube.com/watch?v=mVoPG9HtYF8">leg bone&rsquo;s connected to the knee bone</a>&rdquo;, which I absolutely love. It&rsquo;s a brilliantly designed API; very practical, intuitive, and extensive. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">MDN docs</a> are also fantastic.</p>
<h2 id="tremolo">Tremolo</h2>
<p>Tremolo was the next candidate. It uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode">OscillatorNode</a> to modulate the amplitude (volume/gain) of the input signal. The most challenging part was the depth control. Again, the solution was to split the signal in two, and control the dry/wet signals in inverse quantities. The dry path stayed unaltered, whereas the wet path had the modulated gain node attached. As you mix more wet signal in, you reduce the amount of dry. This creates a less intense sound without increasing the overall volume.</p>
<h2 id="chorus">Chorus</h2>
<p>Chorus was tricky, and to be honest it&rsquo;s the one I&rsquo;m least happy with. Dan from <a href="https://www.youtube.com/watch?v=ni0RtQWWpig">That Pedal Show</a> brilliantly explained the concept of Chorus/Flanging, but I really struggled to emulate it. Modulating the delay time never quite got the right sound, and I ended up using <code>requestAnimationFrame</code> as the <code>OscillatorNode</code> was too aggressive. This is definitely one to refactor and prototype a little more; it could be a great effect and would tick off flanging too.</p>
<h2 id="boost">Boost</h2>
<p>Boost was nice and simple, a single gain node, nothing more. Lovely stuff.</p>
<h2 id="reverb">Reverb</h2>
<p>Reverb was so cool! It uses the <code>ConvolverNode</code> which samples another audio source (referred to as an impulse) and works out the &lsquo;shape&rsquo; of the original sound, making it reproducible. Some kind chap has created a whole host of <a href="https://www.voxengo.com/impulses/">free impulses</a> to use; I opted for the &lsquo;Conic Long Echo Hall&rsquo; to get a nice large reverb. Wiring it up was surprisingly straightforward for what seems likes a complex effect:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">reverb</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createConvolver</span><span class="p">();</span>
<span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;/assets/Conic Long Echo Hall.wav&#39;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">())</span>
  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">decodeAudioData</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">buffer</span> <span class="p">=&gt;</span> <span class="p">{</span>
      <span class="nx">reverb</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">=</span> <span class="nx">buffer</span><span class="p">;</span>
    <span class="p">});</span>
  <span class="p">});</span>
</code></pre></div><p>The final piece of the puzzle was a mix control, created in the same way as the tremolo depth control. I was particularly proud of the name <strong>spacer.gif</strong> too!</p>
<h3 id="reverb-and-delay-tails">Reverb and delay tails</h3>
<p>One particularly challenging feature was Reverb and Delay tails. Tails allow the sound to &lsquo;tail off&rsquo; when you switch the effect pedal off. I was really struggling to work out how to route the audio without rewriting considerable chunks of code. After two failed attempts, I turned to pen and paper, sketching out the audio nodes. As it often tends to go, the solution came to me quickly. There&rsquo;s something enlightening about using paper to solve virtual problems, I find my brain engages differently to that medium.</p>
<p>The solution was to split the signal into wet/dry, and expose an FX send and return from the input switch code. The FX return stays connected to the live output at all times, even when the pedal is off (which seems counter-intuitive at first). But with that in place, we can toggle the input source from wet to dry, and leave the output to provide tails!</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="p">[</span>
  <span class="nx">fxSend</span><span class="p">,</span>
  <span class="nx">fxReturn</span><span class="p">,</span>
  <span class="nx">output</span><span class="p">,</span>
  <span class="nx">toggle</span>
<span class="p">]</span> <span class="o">=</span> <span class="nx">createInputSwitchWithTails</span><span class="p">(</span>
  <span class="nx">input</span><span class="p">,</span>
  <span class="nx">isActive</span>
<span class="p">);</span>
</code></pre></div><h2 id="wah-wah">Wah Wah</h2>
<p><a href="https://www.novis.co/">Tim</a> suggested a Wah pedal after I filled him in on the project. I&rsquo;m pretty happy with the outcome, but it&rsquo;s definitely not as pronounced an effect as I would like. It uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode">BiquadFilterNode</a> in <code>bandpass</code> mode. There&rsquo;s an aggressive Q, and maximum attenuation, but it still sounds quite subtle. I started off with an &lsquo;autowah&rsquo; approach, using another <code>requestAnimationFrame</code> loop, but nothing beats hooking up a real expression pedal&hellip; 🥁</p>
<h3 id="web-midi-api">Web MIDI API</h3>
<p>This is an equally lovely web API. I&rsquo;m using it with very light touches, but it&rsquo;s still a very powerful way to get tactile and tangible hardware interacting with your website. Connecting to MIDI devices is a nice one-liner promise, returning an array of inputs and outputs. You then loop through the inputs and attach our event listener.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="k">try</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">midiCtx</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">navigator</span><span class="p">.</span><span class="nx">requestMIDIAccess</span><span class="p">();</span>

  <span class="nx">midiCtx</span><span class="p">.</span><span class="nx">inputs</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">entry</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">entry</span><span class="p">.</span><span class="nx">onmidimessage</span> <span class="o">=</span> <span class="nx">onMidiMessage</span><span class="p">;</span>
  <span class="p">});</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;No midi connectivity&#39;</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>The handler is pretty low key too. I opted to listen for the two appropriate codes; <strong>144</strong> and <strong>176</strong>, and dispatch custom events onto the window.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">onMidiMessage</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">data</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">===</span> <span class="mi">144</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span><span class="k">new</span> <span class="nx">CustomEvent</span><span class="p">(</span><span class="s1">&#39;MIDI&#39;</span><span class="p">,</span> <span class="p">{</span>
      <span class="nx">detail</span><span class="o">:</span> <span class="nx">data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
    <span class="p">}));</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">===</span> <span class="mi">176</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span><span class="k">new</span> <span class="nx">CustomEvent</span><span class="p">(</span><span class="s1">&#39;MIDIEXP&#39;</span><span class="p">,</span> <span class="p">{</span>
      <span class="nx">detail</span><span class="o">:</span> <span class="nx">data</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
    <span class="p">}));</span>
  <span class="p">}</span>
<span class="p">};</span>
</code></pre></div><p><strong>144</strong> is the &lsquo;Note On&rsquo; message. I use the wonderful <a href="https://loopcommunity.com/en-us/looptimus">Looptimus</a> controller, that sends out digits corresponding to which pedal has been pressed, which in turn can be used to turn the JS pedals on and off!</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;MIDI&#39;</span><span class="p">,</span> <span class="p">({</span> <span class="nx">detail</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">detail</span> <span class="o">===</span> <span class="nx">pedalIndex</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">togglePedal</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div><p><strong>176</strong> is the &lsquo;Control change&rsquo; event which fires every time the expression pedal is moved. It runs from 0 to 127, so we can <a href="/blog/linear-interpolation/">invlerp, then lerp</a> to squash and stretch the values from one data size to another.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;MIDIEXP&#39;</span><span class="p">,</span> <span class="p">({</span> <span class="nx">detail</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">decimal</span> <span class="o">=</span> <span class="nx">invlerp</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">127</span><span class="p">,</span> <span class="nx">detail</span><span class="p">);</span>
  <span class="kr">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">lerp</span><span class="p">(</span><span class="nx">filterMin</span><span class="p">,</span> <span class="nx">filterMax</span><span class="p">,</span> <span class="nx">decimal</span><span class="p">);</span>
  <span class="nx">filter</span><span class="p">.</span><span class="nx">frequency</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">});</span>
</code></pre></div><p>This &lsquo;hands-off&rsquo; approach with events also keeps the rotary knobs working without MIDI, adding control as some form of progressive enhancement.</p>
<h2 id="closures-and-currying">Closures and currying</h2>
<p>I found currying to be a particularly helpful technique for this project. In simple terms, a currying function is one that returns another function, but that&rsquo;s not really doing it justice, so it&rsquo;s worth <a href="https://codeburst.io/callbacks-closures-and-currying-3cc14300686a">reading up on it</a>. Here&rsquo;s one in action:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">myFunction</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">);</span>
  <span class="k">return</span> <span class="p">(</span><span class="nx">b</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>This can be called in a slightly confusing manner:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">myFunction</span><span class="p">(</span><span class="mi">1</span><span class="p">)(</span><span class="mi">2</span><span class="p">);</span>
<span class="c1">// 1
</span><span class="c1">// 1 2
</span></code></pre></div><p>But it can also be written in a much more readable way:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">myFunction</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="c1">// 1
</span><span class="c1"></span><span class="nx">result</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="c1">// 1 2
</span></code></pre></div><p>When you call the first function with some parameters, you &lsquo;inject&rsquo; that function with some state that can be used later. I like to think of them as &lsquo;code grenades&rsquo;. That first call primes the function, pulling out the grenade&rsquo;s safety pin, and the second call sets it off!</p>
<p>Let&rsquo;s take a function for updating an <code>AudioNode</code> or &lsquo;pot&rsquo; (<a href="https://en.wikipedia.org/wiki/Potentiometer">potentiometer</a>). It takes a <code>pot</code> parameter and returns another function expecting a browser event:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">updateNode</span> <span class="o">=</span> <span class="nx">pot</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">event</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">pot</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div><p>We can use it in several ways:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="c1">// Not overly readable
</span><span class="c1"></span><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="nx">event</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">updateNode</span><span class="p">(</span><span class="nx">feedback</span><span class="p">.</span><span class="nx">gain</span><span class="p">)(</span><span class="nx">event</span><span class="p">);</span>
<span class="p">});</span>

<span class="c1">// Getting better
</span><span class="c1"></span><span class="kr">const</span> <span class="nx">potToUpdate</span> <span class="o">=</span> <span class="nx">updateNode</span><span class="p">(</span><span class="nx">feedback</span><span class="p">.</span><span class="nx">gain</span><span class="p">);</span>
<span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="nx">event</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">potToUpdate</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
<span class="p">});</span>

<span class="c1">// So SUCCINCT
</span><span class="c1"></span><span class="kr">const</span> <span class="nx">onInput</span> <span class="o">=</span> <span class="nx">updateNode</span><span class="p">(</span><span class="nx">feedback</span><span class="p">.</span><span class="nx">gain</span><span class="p">);</span>
<span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="nx">onInput</span><span class="p">);</span>
</code></pre></div><p>This means we can move the <code>updateNode(feedback.gain)</code> call into another function that has access the <code>AudioNode</code> in question, and just pass around <code>onInput</code> as a generic event handler. It&rsquo;s a neat way to keep concerns separate, and reduce the mixing of DOM and audio API code.</p>
<h2 id="creating-the-ui">Creating the UI</h2>
<p><a href="https://pedalboard.netlify.app/"><img src="/images/blog/pedals.png" alt="The pedalboard"></a></p>
<p>There wasn&rsquo;t really a conscious decision to write this in vanilla JS, it sort of just happened by extension of hacking and getting a bit carried away.</p>
<p>I did consider using Typescript (to improve IDE intellisense) or Preact (to control DOM elements), but opted it to keep it simple. There&rsquo;s an interesting side effect to using the Web Audio API. Each node holds it&rsquo;s own state, but crucially doesn&rsquo;t emit events like the <code>HTMLMediaElement</code>.</p>
<p>It&rsquo;s one of the reasons I love the <code>&lt;video&gt;</code> and <code>&lt;audio&gt;</code> elements so much. They hold play, time, duration and speed state, but also emit sensible events when any value updates. It means you can separate the trigger from the event. Take a play/pause button that changes it&rsquo;s icon depending on the video state. You could change the icon on the click event, but that&rsquo;ll get out of sync if there are two buttons, or the user clicks directly on the video. The key is to only call <code>video.play()</code> from the trigger, and change the icon(s) on the <code>play</code> and <code>pause</code> events. Sorry, this has gone a bit rambly. The point was, <code>AudioNode</code>s don&rsquo;t have such events, so state effectively has to be managed twice.</p>
<h2 id="making-a-pedal">Making a pedal</h2>
<p>A pedal is composed in a number of steps:</p>
<ul>
<li>Create the audio nodes</li>
<li>Create the on/off switch, including the output gain stage</li>
<li>Set the default node values</li>
<li>Connect the audio nodes together</li>
<li>Create the pedal DOM structure</li>
<li>Create and wire up each rotary knob</li>
</ul>
<p>It&rsquo;s not the most concise setup, but it works. Here&rsquo;s the full code for the boost pedal:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">boostPedal</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">input</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Default settings
</span><span class="c1"></span>  <span class="kr">const</span> <span class="nx">defaults</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">gain</span><span class="o">:</span> <span class="mf">1.5</span><span class="p">,</span>
    <span class="nx">active</span><span class="o">:</span> <span class="kc">false</span>
  <span class="p">};</span>

  <span class="c1">// Create audio nodes
</span><span class="c1"></span>  <span class="kr">const</span> <span class="nx">sum</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>
  <span class="kr">const</span> <span class="nx">boost</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">createGain</span><span class="p">();</span>

  <span class="kr">const</span> <span class="p">[</span><span class="nx">output</span><span class="p">,</span> <span class="nx">toggle</span><span class="p">]</span> <span class="o">=</span> <span class="nx">createInputSwitch</span><span class="p">(</span><span class="nx">input</span><span class="p">,</span> <span class="nx">sum</span><span class="p">,</span> <span class="nx">defaults</span><span class="p">.</span><span class="nx">active</span><span class="p">);</span>

  <span class="c1">// Set default values
</span><span class="c1"></span>  <span class="nx">boost</span><span class="p">.</span><span class="nx">gain</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">defaults</span><span class="p">.</span><span class="nx">gain</span><span class="p">;</span>

  <span class="c1">// Connect the nodes togther
</span><span class="c1"></span>  <span class="nx">input</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">boost</span><span class="p">);</span>
  <span class="nx">boost</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">sum</span><span class="p">);</span>

  <span class="c1">// Create the DOM nodes
</span><span class="c1"></span>  <span class="kr">const</span> <span class="nx">pedal</span> <span class="o">=</span> <span class="nx">createPedal</span><span class="p">({</span>
    <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;boost&#39;</span><span class="p">,</span>
    <span class="nx">label</span><span class="o">:</span> <span class="s1">&#39;!important&#39;</span><span class="p">,</span>
    <span class="nx">toggle</span><span class="p">,</span>
    <span class="nx">active</span><span class="o">:</span> <span class="nx">defaults</span><span class="p">.</span><span class="nx">active</span><span class="p">,</span>
    <span class="nx">index</span>
  <span class="p">});</span>

  <span class="nx">createRotaryKnob</span><span class="p">({</span>
    <span class="nx">pedal</span><span class="p">,</span>
    <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;boost&#39;</span><span class="p">,</span>
    <span class="nx">label</span><span class="o">:</span> <span class="s1">&#39;Boost&#39;</span><span class="p">,</span>
    <span class="nx">max</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="nx">onInput</span><span class="o">:</span> <span class="nx">updatePot</span><span class="p">(</span><span class="nx">boost</span><span class="p">.</span><span class="nx">gain</span><span class="p">),</span>
    <span class="nx">value</span><span class="o">:</span> <span class="nx">defaults</span><span class="p">.</span><span class="nx">gain</span>
  <span class="p">});</span>

  <span class="nx">$pedalboard</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">pedal</span><span class="p">);</span>

  <span class="k">return</span> <span class="nx">output</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div><h2 id="input-choice">Input choice</h2>
<p>One neat browser feature I leaned on was the <code>[type=&quot;range&quot;]</code> input - it&rsquo;s powering every rotary knob. You pass it a min, max, and step and it creates a nice draggable slider. The only downside is that it doesn&rsquo;t look like a rotary knob. Regardless, it still has merit, both from an accessibility standpoint, and as a great way to cap min and max values. A range input will only accept values that meet the min, max and step criteria, which makes it perfect to accept values generated by the dragged rotary knob, and ensure no dodgy values get through to the Web Audio API nodes.</p>
<h2 id="summing-up-and-future-plans">Summing up and future plans</h2>
<p>I&rsquo;m very happy with how this three day hack has come together. It was a magical feeling the plug my guitar into the browser for the first time. It felt so tangible, and so real to create a virtual thing interacting with a physical thing. And that feeling was mirrored when plugging the MIDI devices in. I&rsquo;d definitely love to explore this space a bit more.</p>
<p>The chorus pedal doesn&rsquo;t sound great, so that should be the next point of focus. I&rsquo;d also like to get some animations onto the pedals, and maybe some live visualisations. A loop pedal would also be incredible. But who knows, I&rsquo;m chuffed with the progress so far and any more will be a win.</p>
<p>Disclaimer, I&rsquo;ve only really tested this in Chrome and Firefox, plus Web Audio API support is limited, so apologies for the other browsers out there 😐 - It&rsquo;s also not hugely optimised for mobile yet!</p>
<p>The code is also visible on <a href="https://github.com/trys/pedalboard">GitHub</a>, and uses <a href="https://sergey.trysmudford.com">Sergey</a> for compilation.</p>
<a href="https://pedalboard.netlify.app/" class="button">Try the pedalboard out!</a>

<hr>
<h3 id="footnotes">Footnotes</h3>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">MDN docs</a> for Web Audio API are truly exceptional. Check them out!</p>
<p><a href="https://pedals.io/">Pedals.io</a> looks like an incredible complete pedalboard setup, and considerably more refined than my efforts!</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Sergey &#43; Markdown &#43; Tests &#43; Folders &#43; ENV &#43; Exclusion</title>
      <link>https://www.trysmudford.com/blog/sergey-markdown/</link>
      <pubDate>Tue, 23 Apr 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/sergey-markdown/</guid>
      <description><![CDATA[
<p>After a bank holiday weekend of hacking, <a href="https://sergey.trysmudford.com">Sergey</a> has some exciting new features!</p>
<h2 id="markdown-">Markdown 📝</h2>
<p>Markdown is one of those developer experience features that usually goes hand-in-hand with a static site generator. But it was important to me for it to be an opt-in feature.</p>
<p>Sergey doesn&rsquo;t profess to be the SSG for everyone, quite the opposite. It&rsquo;s intenionally small in scope, and will never do half the clever things many SSG&rsquo;s do. <a href="https://sergey.trysmudford.com/#what-is-sergey">Sergey&rsquo;s goal</a> is as much for those who&rsquo;ve never used a SSG before, as it is for those who need a prototyping tool before picking a full generator. So markdown support had to subscribe to that philosophy (as with all new features).</p>
<p><a href="https://twitter.com/andybelldesign/status/1118062931747512320">Andy Bell</a> suggested the lovely syntax, extending the existing <code>&lt;sergey-import&gt;</code> to include an <code>as</code> attribute. This&rsquo;ll also open up any future content types without the need for extra HTML tags.</p>
<p>So here&rsquo;s how it works:</p>
<ol>
<li>Add a markdown file to your <code>_imports</code> folder.</li>
<li>Include the content with:</li>
</ol>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;about&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;markdown&#34;</span> <span class="p">/&gt;</span>
</code></pre></div><p>Full documentation is available <a href="https://sergey.trysmudford.com/markdown/">here</a>.</p>
<h2 id="tests-">Tests! 🙌</h2>
<p>Another; less glamorous but crucial, update was adding some unit testing. Tests will add confidence to future releases, ensuring nothing&rsquo;s been broken by accident. For now, there are tests for imports, slots, templates and markdown, but I&rsquo;ll be looking into some file-based tests shortly.</p>
<p>The next feature will be to allow imports and markdown files to exist in folders. Hopefully it won&rsquo;t be a major job, but it&rsquo;ll probably involve refactoring the recursive file code, and should make the file watching a bit more reliable. Currently, new files don&rsquo;t get picked up in dev mode without a server restart - not ideal.</p>
<h2 id="import-folders-">Import folders 🗂</h2>
<p>To coincide with the markdown release, Sergey now allows you to nest your partials (and markdown files) into folders.</p>
<p>A header file in <code>_imports/partials/header</code> would be included with:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;partials/header&#34;</span> <span class="p">/&gt;</span>
</code></pre></div><p>You can also override the content folder (also <code>_imports</code> by default), to be nested within the imports folder, or totally separate, it&rsquo;s up to you!</p>
<p>If you set the content folder to be <code>_imports/content</code>, you wouldn&rsquo;t need specify the <code>content</code> bit in the import <code>src</code>, simply carry on with imports like:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;aboutMarkdownFile&#34;</span> <span class="p">/&gt;</span>
</code></pre></div><h2 id="env-">.env 🗝</h2>
<p>You&rsquo;ve been able to pass arguments to the <code>sergey</code> command from day one, but now you can prebake those values with a <code>.env</code> file. All the options have been documented on the <a href="https://sergey.trysmudford.com/options/">options page</a>.</p>
<h2 id="exclusion-">Exclusion 🚫</h2>
<p>Finally, Sergey now ignores all entries in your <code>.gitignore</code>, making compilation that bit quicker. You can also pass in additional folders and files to exclude from being watched in dev mode and copied at build time.</p>
<h2 id="summing-up-">Summing up ✨</h2>
<p>If you have any questions, spot any bugs, or think of an feature requests, please send me a message on <a href="https://twitter.com/trysmudford">Twitter</a>.</p>
]]>
      </description>
    </item>
    
    <item>
      <title>A talk at Codebar Brighton</title>
      <link>https://www.trysmudford.com/blog/a-talk-at-codebar-brighton/</link>
      <pubDate>Wed, 17 Apr 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/a-talk-at-codebar-brighton/</guid>
      <description><![CDATA[
<p>I gave a talk about <a href="https://sergey.trysmudford.com/">Sergey</a> at <a href="https://codebar.io/brighton">Codebar Brighton</a> on Tuesday night! Other than a post-hackathon recap, this was my first foray into public speaking - and for a first go with limited preparation time, I think it went <a href="https://twitter.com/CodebarBrighton/status/1118504901301157889">pretty well</a>!</p>
<p>I didn&rsquo;t clam up too much, nor lose my train of thought, and the live coding (yep&hellip;) went without a hitch!</p>
<p>The slot was only ~10 minutes and I had quite a lot to get through:</p>
<ul>
<li>What is Sergey?</li>
<li>What is a static site generator?</li>
<li>When is a SSG useful?</li>
<li>What other SSG options are there?</li>
<li>How do you use Sergey?</li>
</ul>
<p>The first four points were covered by a PowerPoint presentation, but much as I tried to avoid it, live coding was required for the demonstration. I took a two page website made in HTML, and extracted the header, footer and head into importable components. The latter also used a <code>&lt;sergey-slot /&gt;</code> for good measure!</p>
<p>I found out about the talk on Friday afternoon, so time was quite limited to prepare; fortunately I was quite comfortable talking about Sergey, given how new a project it is. In hindsight, I should&rsquo;ve prepared a more thorough &lsquo;script&rsquo; for the first section, and definitely had a round-up to close off the talk. Fortunately Cassie was on hand to point people towards the website and documentation! 🙈</p>
<p>Regardless, it was a really fun night, and a great, encouraging and safe event to speak at.</p>
<p>After the stress of the talk, I joined the coaching team, running through an introduction to JavaScript. We built a little to-do list application and and got practical experience of <code>querySelector</code>, <code>addEventListener</code>, <code>preventDefault</code>, <code>createElement</code>, and <code>appendChild</code>!</p>
<p>We took on a final feature in the last 20 minutes, which was probably a stretch time-wise. It probably would&rsquo;ve been better to finally run through of the night&rsquo;s coding, and add some reminder comments. So plenty to learn from!</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Named slots with Sergey</title>
      <link>https://www.trysmudford.com/blog/named-slots-with-sergey/</link>
      <pubDate>Sat, 13 Apr 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/named-slots-with-sergey/</guid>
      <description><![CDATA[
<p>Sergey launched with two main features:</p>
<ul>
<li>Imports: <code>&lt;sergey-import src=&quot;&quot; /&gt;</code></li>
<li>Basic slots: <code>&lt;sergey-slot /&gt;</code></li>
</ul>
<p>It was intentionally small in scope to: <strong>a.</strong> get something out there quickly to see if it&rsquo;s a useful tool, and <strong>b.</strong> not take on too many tasks and execute them badly.</p>
<p>Reception has been very positive, answering <strong>a.</strong> so I can get on with adding a few new features. Not many mind, Sergey is never going to be a big static site generator.</p>
<p>The first feature request was for named slots. It&rsquo;s something Jeremy and I discussed when it was first demo&rsquo;d, but the MVP nature of the launch dictated the decision to hold it back.</p>
<h2 id="syntax">Syntax</h2>
<p>I took the syntax inspiration from Vue.js. Given Sergey already uses <code>&lt;sergey-slot /&gt;</code>, adding a name attribute, and a corresponding <code>&lt;sergey-template /&gt;</code> tag seemed like the most sensible route. Here are the new tags:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">sergey-template</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;slotName&#34;</span><span class="p">&gt;</span>Content<span class="p">&lt;/</span><span class="nt">sergey-template</span><span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">sergey-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;slotName&#34;</span><span class="p">&gt;</span>Fallback content<span class="p">&lt;/</span><span class="nt">sergey-slot</span><span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">sergey-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;slotName&#34;</span> <span class="p">/&gt;</span>
</code></pre></div><p>Named slots open up opportunities for creating page templates with Sergey. Here&rsquo;s an example <code>template.html</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;en&#34;</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;viewport&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;width=device-width, initial-scale=1.0&#34;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;&lt;</span><span class="nt">sergey-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;title&#34;</span> <span class="p">/&gt;&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;header&#34;</span> <span class="p">/&gt;</span>

    <span class="p">&lt;</span><span class="nt">main</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;&lt;</span><span class="nt">sergey-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;title&#34;</span> <span class="p">/&gt;&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">sergey-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;content&#34;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>

    <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;footer&#34;</span> <span class="p">/&gt;</span>
  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><p>With that, we can compose a full page with a couple of template tags:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;template&#34;</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-template</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;title&#34;</span><span class="p">&gt;</span>My page title<span class="p">&lt;/</span><span class="nt">sergey-template</span><span class="p">&gt;</span>

  <span class="p">&lt;</span><span class="nt">sergey-template</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;content&#34;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Page content<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>goes here<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">sergey-template</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">sergey-import</span><span class="p">&gt;</span>
</code></pre></div><hr>
<h2 id="building-it">Building it</h2>
<p>Tackling named slots wasn&rsquo;t as challenging as I first feared. But it was only when I started building use-cases for the documentation that I spotted some cracks in the overall foundations! 😬</p>
<p>The compilation step for Sergey happened in two phases, once for imports, and again for each file. This worked for all the examples I&rsquo;d written so far. But once the templates got a bit deeper, it quickly became apparent that it wasn&rsquo;t good enough. I spent the rest of the evening mulling over the problem, and writing psuedo-code as the solution became clearer.</p>
<p>The key was recursion. The parser needed to run for every import it came across, calling itself on each iteration. Slots could then be properly calculated and nested imports returned correctly. Much like <code>while</code> loops, recursion can be a scary prospect, but fortunately, barring a little regex trickery, it all came together okay.</p>
<p>I finished off by writing up <a href="https://sergey.trysmudford.com/slots/#named-slots">the documentation</a> for named slots. I&rsquo;m still not sure how casual to make the docs. I think there&rsquo;s value in walkthrough examples, but maybe there needs to be some more cold hard facts. Let me know if you have any preferences. It&rsquo;s also time to decide what feature to add next - there&rsquo;s quite a bit to do on <a href="https://github.com/trys/sergey/projects/1">the roadmap</a>!</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Sergey</title>
      <link>https://www.trysmudford.com/blog/sergey/</link>
      <pubDate>Sat, 13 Apr 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/sergey/</guid>
      <description><![CDATA[
<p>Yesterday saw the launch of <a href="https://sergey.trysmudford.com">Sergey</a>, a tiny little static site generator. It&rsquo;s been around a week in the making, with the idea triggered by discussions with Cassie at <a href="https://indieweb.org/Homebrew_Website_Club#Brighton">Homebrew website club</a> (come along, it&rsquo;s great!).</p>
<p>We were chatting about Michelle&rsquo;s wonderful <a href="https://michellebarker.co.uk/">new site</a>, built <a href="https://twitter.com/mbarker_84/status/1107416868711743490">entirely in HTML and CSS</a>. It&rsquo;s so refreshing to see such clean, semantic and understandable markup in this world/<a href="https://adactio.com/journal/15011">bubble</a> of complexity. I mean, please <code>view-source</code> <a href="https://michellebarker.co.uk/">the website</a>, doesn&rsquo;t it spark joy?! 😍</p>
<p>This HTML &amp; CSS only approach is great for very small sites, but as soon as the number of pages increases to beyond one or two, you&rsquo;ll quickly find yourself copying and pasting global markup to keep the pages in sync.</p>
<p>We came across a similar conundrum at daisie, specifically for the <a href="https://ldncreates.daisie.com/">LDNCreates campaign site</a>. Emma built it with classic HTML + CSS, and it works brilliantly! At the moment it&rsquo;s a one-pager, but sub-pages are in the pipeline. This sounded like a job for a static site generator (SSG), but which one to choose?!</p>
<p>The deadline was <em>really</em> tight for that project, so time couldn&rsquo;t be spent <a href="https://www.staticgen.com/">researching SSG&rsquo;s</a>, testing them for viability and weighing up the pro&rsquo;s and con&rsquo;s. And that&rsquo;s the same for <a href="/blog/city-life/">a lot of developers</a>. Funny how things keep coming back around to bubbles! Many simply don&rsquo;t have the luxury of time to invest in that research phase, so keep with the tried and true methods, and lo, out pops another PHP site (for the record I have zero problems with PHP, it&rsquo;s my bread and butter).</p>
<p>LDNCreates went with <a href="https://gohugo.io/">Hugo</a>, but it&rsquo;s only using a <strong>fraction</strong> of what Hugo is capable of. That&rsquo;s not a problem, per se, more of a shame to use a hammer to crack a nut.</p>
<p>What this project needed was: <strong>HTML plus some includable partials</strong>. To be fair to PHP, it&rsquo;s something it did really well, but not everyone has a PHP dev envinronment to hand, and again, nor the luxury of time to set it up.</p>
<p>Cassie mentioned that from her <a href="https://codebar.io/brighton">Codebar</a> experience, there&rsquo;d definitely be a market for a SSG like that. So that night, I made a start!</p>
<hr>
<p>Sergey (thanks for the name <a href="https://adactio.com">Jeremy</a>!), has two main features exposed via custom tags:</p>
<ul>
<li>Imports: <code>&lt;sergey-import src=&quot;&quot; /&gt;</code></li>
<li>Slots: <code>&lt;sergey-slot /&gt;</code></li>
</ul>
<h2 id="basic-imports">Basic imports</h2>
<p>The <code>&lt;sergey-import /&gt;</code> tag has one attribute: <code>src</code>.</p>
<p><code>&lt;sergey-import src=&quot;footer&quot; /&gt;</code> will pull in the file: <code>_imports/footer.html</code> and inject it in place of the tag. So with the following markup:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;footer&#34;</span> <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /_imports/footer.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">footer</span><span class="p">&gt;</span>
  <span class="ni">&amp;copy;</span> 2019
<span class="p">&lt;/</span><span class="nt">footer</span><span class="p">&gt;</span>
</code></pre></div><p>Sergey will merge the two together and create:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /public/index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">footer</span><span class="p">&gt;</span>
    <span class="ni">&amp;copy;</span> 2019
  <span class="p">&lt;/</span><span class="nt">footer</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</code></pre></div><h2 id="slots">Slots</h2>
<p>Within any import file, you can place a <code>&lt;sergey-slot /&gt;</code> tag. This slot allows you to inject custom content into your generic import files. Here&rsquo;s a header example:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /_imports/header.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Page title<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</code></pre></div><p>This isn&rsquo;t a very useful component, as we can&rsquo;t use different titles per page. Slots make that possible:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /_imports/header.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-slot</span> <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</code></pre></div><p>To inject content, we expand <code>&lt;sergey-import /&gt;</code> to be a full tag; rather than a self-closing one, and put the custom content within:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">main</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;header&#34;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>My custom page title<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">sergey-import</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>
</code></pre></div><p>And all together, it&rsquo;ll create:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /public/index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">main</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>My custom page title<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>
</code></pre></div><h2 id="slot-fallback-content">Slot fallback content</h2>
<p>The final Sergey feature (for now!) is &lsquo;default slots&rsquo; or &lsquo;slot fallbacks&rsquo;. Wherever you&rsquo;ve used a slot, you can provide some default content that&rsquo;ll be shown if you don&rsquo;t provide slot content. Let&rsquo;s go for a <code>&lt;head&gt;</code> example, and create a default <code>&lt;title&gt;</code> tag:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /_imports/head.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-slot</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Default title<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">sergey-slot</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</code></pre></div><p>If we leave the <code>&lt;sergey-import src=&quot;head&quot; /&gt;</code> as a self-closing tag, it&rsquo;ll render the fallback title:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;head&#34;</span> <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /public/index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Default title<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><p>But by passing in some content, it&rsquo;ll override that default slot content:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /about/index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;head&#34;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>About title<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">sergey-import</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /public/about/index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>About title<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><h2 id="all-together-now">All together now!</h2>
<p>Let&rsquo;s make a final page to show all the bits in action:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;head&#34;</span> <span class="p">/&gt;</span>

  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">main</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;header&#34;</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>My custom page title<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
      <span class="p">&lt;/</span><span class="nt">sergey-import</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>

    <span class="p">&lt;</span><span class="nt">sergey-import</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;footer&#34;</span> <span class="p">/&gt;</span>
  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="c">&lt;!-- /public/index.html --&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Default title<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>

  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">main</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>My custom page title<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
      <span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>

    <span class="p">&lt;</span><span class="nt">footer</span><span class="p">&gt;</span>
      <span class="ni">&amp;copy;</span> 2019
    <span class="p">&lt;/</span><span class="nt">footer</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><h2 id="have-a-go">Have a go!</h2>
<p>To act as a starting point, I&rsquo;ve created an example website that can be deployed straight to Netlify. You can also have a root around the code <a href="https://github.com/trys/sergey-netlify">here</a>. The <a href="https://sergey.trysmudford.com/">sergey.trysmudford.com</a> site is naturally written with Sergey, so that can be <a href="https://github.com/trys/sergey/tree/master/example">checked out</a> too.</p>
<p><a href="https://app.netlify.com/start/deploy?repository=https://github.com/netlify/sergey-netlify"><img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify"></a></p>
]]>
      </description>
    </item>
    
  </channel>
</rss>