<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Down the rabbithole – a frontend blog</title>
  <subtitle>A frontend blog (mostly)</subtitle>
  <link href="https://eszter.space/feed.xml" rel="self"/>
  <link href="https://eszter.space/"/>
  <updated>2025-02-23T00:00:00+00:00</updated>
  <id>https://eszter.space/</id>
  <author>
    <name>c0derabbit</name>
    <email>ekov@pm.me</email>
  </author>
  
  <entry>
    <title>Why</title>
    <link href="https://eszter.space/why/"/>
    <updated>2017-09-29T08:53:00+00:00</updated>
    <id>https://eszter.space/why/</id>
    <content type="html">&lt;p&gt;A few reasons why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I forget stuff.&lt;/li&gt;
&lt;li&gt;I prefer sharing stuff here than on social media.&lt;/li&gt;
&lt;li&gt;If this helps anyone, I helped someone. &amp;lt;3&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Flexbox with fixed sidebar</title>
    <link href="https://eszter.space/sidebar/"/>
    <updated>2018-05-02T00:00:00+00:00</updated>
    <id>https://eszter.space/sidebar/</id>
    <content type="html">&lt;p&gt;I love &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox&quot;&gt;flexbox&lt;/a&gt;. It’s… flexible. Today, developers have to think about almost as many resolutions as devices. Things have to shrink (and grow!), fit, and not cover anything else. In some cases, they have to reorder themselves (more on that in a future blog post). Flexbox can do all this. One caveat though — it does not work well with absolute or fixed positioning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So how do we keep a sidebar in place?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;html-structure&quot;&gt;HTML structure &lt;a class=&quot;header-anchor&quot; href=&quot;#html-structure&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our very simple website will contain a sidebar and a &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; part. These will be wrapped in a common parent, which has &lt;code&gt;display: flex&lt;/code&gt; property. Something like this (fill &lt;code&gt;main&lt;/code&gt; with lots of paragraphs —  try &lt;a href=&quot;http://spaceipsum.com/&quot;&gt;Space Ipsum&lt;/a&gt; or &lt;a href=&quot;http://fillerama.io/&quot;&gt;Fillerama&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;wrapper&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;nav&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;sidebar&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Links...&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;nav&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;main&lt;/span&gt;&amp;gt;&lt;/span&gt;Fill with lots of text&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;main&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;CSS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.wrapper&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;display&lt;/span&gt;: flex;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Blocks have an &lt;code&gt;overflow&lt;/code&gt; property. They also have &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;, and with these properties, we can easily tame that sidebar.&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-sidebar&quot;&gt;Fixing the sidebar &lt;a class=&quot;header-anchor&quot; href=&quot;#fixing-the-sidebar&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, our sidebar should not be taller than the screen. But how tall is the screen? It’s exactly &lt;code&gt;100vh&lt;/code&gt;. Now that we are at it, let’s fix &lt;code&gt;box-sizing&lt;/code&gt; for &lt;em&gt;every element&lt;/em&gt; too, in case there are any paddings or borders&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;* {
  &lt;span class=&quot;hljs-attribute&quot;&gt;box-sizing&lt;/span&gt;: border-box;
}

&lt;span class=&quot;hljs-selector-class&quot;&gt;.sidebar&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;height&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;100vh&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;scrolling-the-content&quot;&gt;Scrolling the content &lt;a class=&quot;header-anchor&quot; href=&quot;#scrolling-the-content&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point, our sidebar will be as tall as the screen — but our page height is defined by the length of its content. In other words, &lt;code&gt;.wrapper&lt;/code&gt; will be as tall as &lt;code&gt;main&lt;/code&gt;, and if we have lots of paragraphs, we will scroll past the sidebar quickly.
Overflow to the rescue! Let’s make this &lt;code&gt;main&lt;/code&gt; no taller than the screen, and scroll if it has more content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;main&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;height&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;100vh&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;overflow&lt;/span&gt;: scroll;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! &lt;a href=&quot;https://jsfiddle.net/rgvpn1zr/1/&quot;&gt;Here’s a working example.&lt;/a&gt;&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;see &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing&quot;&gt;box-sizing on MDN web docs&lt;/a&gt; &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>SVG animations with CSS</title>
    <link href="https://eszter.space/svg-animations/"/>
    <updated>2018-05-10T00:00:00+00:00</updated>
    <id>https://eszter.space/svg-animations/</id>
    <content type="html">&lt;p&gt;SVGs are lovely: they are small, scalable, and easily editable by a developer. They can also be animated using CSS, which means that while keeping their tiny size, they can do much better than a GIF.&lt;!-- more --&gt; Need those champagne bubbles to bubble a little faster? No problem. &lt;strong&gt;Whatever the designer suggests can be done with a few lines of CSS, without &lt;sup&gt;significantly&lt;/sup&gt; modifying the original asset.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-main-ingredients-keyframes-and-css-animation&quot;&gt;The main ingredients: @keyframes and CSS animation &lt;a class=&quot;header-anchor&quot; href=&quot;#the-main-ingredients-keyframes-and-css-animation&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/%40keyframes&quot;&gt;&lt;code&gt;@keyframes&lt;/code&gt;&lt;/a&gt; are a way to define animation functions. They need a name, a few timing points (at least a &lt;code&gt;from&lt;/code&gt; and a &lt;code&gt;to&lt;/code&gt;, or percentages), and instructions on where to be at each of these time points.&lt;/p&gt;
&lt;p&gt;CSS property &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/animation&quot;&gt;&lt;code&gt;animation&lt;/code&gt;&lt;/a&gt; takes name, duration, timing function (default: &lt;code&gt;ease&lt;/code&gt;), delay, iteration count (default: &lt;code&gt;1&lt;/code&gt;; number or &lt;code&gt;infinite&lt;/code&gt;) etc.&lt;/p&gt;
&lt;p&gt;Take this beating heart as an example (it’s &lt;code&gt;11.15KB&lt;/code&gt;):&lt;/p&gt;
&lt;style type=&quot;text/css&quot;&gt;
  .svg {
    width: 120px;
    margin: 0 auto;    
  }
  .heart {
    animation: pulse .6s ease-in infinite alternate;
  }

  @keyframes pulse {
    from {transform: scale(.94);}
    to {transform: scale(1);}
  }
&lt;/style&gt;
&lt;div style=&quot;display: flex; flex-direction: column; align-items: center; max-width: 550px;&quot;&gt;
&lt;img src=&quot;https://eszter.space/img/heart.svg&quot; class=&quot;heart svg&quot;&gt;
&lt;div style=&quot;font-size: 7.5px; opacity: .6; margin-top: 1em&quot;&gt;Heart icon by &lt;a href=&quot;https://www.flaticon.com/authors/kirill-kazachek&quot; title=&quot;Kirill Kazachek&quot;&gt;Kirill Kazachek&lt;/a&gt; from &lt;a href=&quot;https://www.flaticon.com/&quot; title=&quot;Flaticon&quot;&gt;flaticon.com&lt;/a&gt; is licensed by &lt;a href=&quot;http://creativecommons.org/licenses/by/3.0/&quot; title=&quot;Creative Commons BY 3.0&quot; target=&quot;\_blank&quot; data-id=&quot;\_&quot;&gt;CC 3.0 BY&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here’s the code itself — you can check the source, I’m not cheating ;)&lt;/p&gt;
&lt;h4&gt;HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;heart.svg&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;heart&amp;quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;CSS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.heart&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;width&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;120px&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: pulse .&lt;span class=&quot;hljs-number&quot;&gt;6s&lt;/span&gt; ease-in infinite alternate;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;@keyframes&lt;/span&gt; pulse {
  from {
    &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: scale(.&lt;span class=&quot;hljs-number&quot;&gt;94&lt;/span&gt;);
  }

  to {
    &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: scale(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;thats-boring-is-that-all-svg-can-do&quot;&gt;That’s boring. Is that all SVG can do? &lt;a class=&quot;header-anchor&quot; href=&quot;#thats-boring-is-that-all-svg-can-do&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Of course not.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Scalable_Vector_Graphics&quot;&gt;SVG&lt;/a&gt;s are written in XML, and can contain paths, texts, shapes (lines, circles etc.) — and &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt; for groups of these. We just have to name these building blocks (with CSS classes and IDs) to animate them separately.&lt;/p&gt;
&lt;p&gt;Consider this image:&lt;/p&gt;
&lt;div style=&quot;display: flex; flex-direction: column; align-items: center; max-width: 550px;&quot;&gt;
&lt;img src=&quot;https://eszter.space/img/rain.svg&quot; class=&quot;svg&quot;&gt;
&lt;div style=&quot;font-size: 7.5px; opacity: .6; margin-top: 1em&quot;&gt;Cloud icon by &lt;a href=&quot;http://www.freepik.com/&quot; title=&quot;Freepik&quot;&gt;Freepik&lt;/a&gt; from &lt;a href=&quot;https://www.flaticon.com/&quot; title=&quot;Flaticon&quot;&gt;flaticon.com&lt;/a&gt; is licensed by &lt;a href=&quot;http://creativecommons.org/licenses/by/3.0/&quot; title=&quot;Creative Commons BY 3.0&quot; target=&quot;\_blank&quot; data-id=&quot;2\_&quot;&gt;CC 3.0 BY&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Its source code has the following structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;svg&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- cloud in the back --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- cloud in the front --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;g&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- cloud fluffs --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- rain drops --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- ...and many more of them --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;g&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;svg&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would like the little rain drops to fall, and the two clouds to float.&lt;br&gt;
For this, we will have to slightly modify the original SVG. Namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The front cloud is made up of several shapes, let’s put them in a group so that they can move together&lt;/li&gt;
&lt;li&gt;The back cloud has to float, let’s give it an ID as well&lt;/li&gt;
&lt;li&gt;Rain drops will have to do the same thing, but timed randomly, so each of them will need an ID.&lt;/li&gt;
&lt;li&gt;I will also make the back cloud a bit darker, because why not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An illustration of these changes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;svg&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- cloud in the back: id added --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cloud-back&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;fill:#8BbbD8;&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- cloud in the front: grouped --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cloud-front&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- cloud fluffs added to front cloud&amp;#x27;s group --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;g&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- rain drops are all individuals, but if we are lazy, we can &amp;quot;class&amp;quot;
  a few together. some will move at the same time, but it&amp;#x27;s fine --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;drop1&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;drop2&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;drop3&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;drop2&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;d&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- and so forth, quasi-randomly assigning classes --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;svg&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we just have to add some css animations, and we’re done.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;hljs-selector-id&quot;&gt;#cloud-front&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: float &lt;span class=&quot;hljs-number&quot;&gt;3s&lt;/span&gt; ease-in-out infinite alternate;
}

&lt;span class=&quot;hljs-selector-id&quot;&gt;#cloud-back&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translateX(&lt;span class=&quot;hljs-number&quot;&gt;12px&lt;/span&gt;);
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: float2 &lt;span class=&quot;hljs-number&quot;&gt;2.2s&lt;/span&gt; .&lt;span class=&quot;hljs-number&quot;&gt;2s&lt;/span&gt; ease-in-out infinite alternate;
}

&lt;span class=&quot;hljs-selector-class&quot;&gt;.drop1&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: rainfall .&lt;span class=&quot;hljs-number&quot;&gt;5s&lt;/span&gt; linear infinite;
}

&lt;span class=&quot;hljs-selector-class&quot;&gt;.drop2&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: rainfall .&lt;span class=&quot;hljs-number&quot;&gt;6s&lt;/span&gt; .&lt;span class=&quot;hljs-number&quot;&gt;1s&lt;/span&gt; linear infinite;
}

&lt;span class=&quot;hljs-selector-class&quot;&gt;.drop3&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: rainfall .&lt;span class=&quot;hljs-number&quot;&gt;7s&lt;/span&gt; .&lt;span class=&quot;hljs-number&quot;&gt;3s&lt;/span&gt; linear infinite;
}

&lt;span class=&quot;hljs-selector-class&quot;&gt;.drop4&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: rainfall .&lt;span class=&quot;hljs-number&quot;&gt;6s&lt;/span&gt; .&lt;span class=&quot;hljs-number&quot;&gt;4s&lt;/span&gt; linear infinite;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;@keyframes&lt;/span&gt; float {
  from { &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translateX(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;);   }
  to   { &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translateX(&lt;span class=&quot;hljs-number&quot;&gt;22px&lt;/span&gt;);}
}

&lt;span class=&quot;hljs-keyword&quot;&gt;@keyframes&lt;/span&gt; float2 {
  from { &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translateX(&lt;span class=&quot;hljs-number&quot;&gt;14px&lt;/span&gt;);}
  to   { &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translateX(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;);   }
}

&lt;span class=&quot;hljs-keyword&quot;&gt;@keyframes&lt;/span&gt; rainfall {
  from {
    &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translate(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;);
    &lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
  }

  to {
    &lt;span class=&quot;hljs-attribute&quot;&gt;transform&lt;/span&gt;: translate(-&lt;span class=&quot;hljs-number&quot;&gt;15px&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;30px&lt;/span&gt;);
    &lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: .&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;
  #cloud-front {
    animation: float 3s ease-in-out infinite alternate;
  }

  #cloud-back {
    transform: translateX(12px);
    animation: float2 2.2s .2s ease-in-out infinite alternate;
  }

  .drop1 {
    animation: rainfall .5s linear infinite;
  }

  .drop2 {
    animation: rainfall .6s .1s linear infinite;
  }

  .drop3 {
    animation: rainfall .7s .3s linear infinite;
  }

  .drop4 {
    animation: rainfall .6s .4s linear infinite;
  }

  @keyframes float {
    from {transform: translateX(0);}
    to {transform: translateX(22px);}
  }

  @keyframes float2 {
    from {transform: translateX(14px);}
    to {transform: translateX(0);}
  }

  @keyframes rainfall {
    from {
      transform: translate(0, 0);
      opacity: 1;
    }
    to {
      transform: translate(-15px, 30px);
      opacity: .1;
    }
  }
&lt;/style&gt;
&lt;div style=&quot;display: flex; flex-direction: column; align-items: center; max-width: 550px;&quot;&gt;

&lt;!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --&gt;
&lt;svg version=&quot;1.1&quot; id=&quot;rain&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; x=&quot;0px&quot; y=&quot;0px&quot; width=&quot;120px&quot; height=&quot;120px&quot; viewBox=&quot;0 0 552.001 512.001&quot; style=&quot;enable-background:new 0 0 512.001 512.001;&quot; xml:space=&quot;preserve&quot;&gt;
&lt;path id=&quot;cloud-back&quot; style=&quot;fill:#8BbbD8;&quot; d=&quot;M330.066,91.981c-2.678-2.507-4.528-5.757-5.264-9.351c-8.552-41.775-45.509-73.201-89.813-73.201
	c-12.92,0-25.215,2.675-36.364,7.499c-3.529,1.527-7.579,1.276-10.814-0.802c-10.603-6.81-23.21-10.768-36.748-10.768
	c-34.131,0-62.393,25.094-67.362,57.837c-0.626,4.125-3.896,7.318-8.039,7.804c-39.655,4.654-70.426,38.35-70.426,79.255l0,0
	c0,9.332,7.565,16.896,16.896,16.896h316.324c9.332,0,16.896-7.565,16.896-16.896l0,0
	C355.352,127.26,345.624,106.546,330.066,91.981z&quot;&gt;&lt;/path&gt;
&lt;g id=&quot;cloud-front&quot;&gt;
	&lt;path style=&quot;fill:#B9E8EF;&quot; d=&quot;M475.023,185.235c-3.916-3.666-6.622-8.42-7.698-13.675
		c-12.506-61.091-66.552-107.048-131.34-107.048c-18.894,0-36.874,3.912-53.177,10.966c-5.161,2.233-11.083,1.865-15.815-1.173
		c-15.506-9.958-33.942-15.747-53.739-15.747c-49.912,0-91.241,36.696-98.508,84.579c-0.915,6.032-5.697,10.702-11.756,11.413
		C44.999,161.356,0,210.633,0,270.451l0,0c0,13.646,11.062,24.709,24.709,24.709h462.583c13.646,0,24.709-11.063,24.709-24.709l0,0
		C512,236.827,497.774,206.535,475.023,185.235z&quot;&gt;&lt;/path&gt;
	&lt;path style=&quot;fill:#ABDFEB;&quot; d=&quot;M207.779,58.722c-47.432,2.57-86.035,38.296-93.034,84.416c-0.915,6.032-5.697,10.702-11.756,11.413
		C44.999,161.356,0,210.633,0,270.451l0,0c0,13.646,11.062,24.709,24.709,24.709h235.938
		C153.418,256.289,161.934,99.33,207.779,58.722z&quot;&gt;&lt;/path&gt;
	&lt;path style=&quot;fill:#8BCDD8;&quot; d=&quot;M147.178,237.838c-4.036,0-7.368-3.209-7.492-7.271c-0.591-19.344,11.238-36.79,29.435-43.414
		c14.724-5.358,31.425-2.537,43.588,7.364c3.212,2.615,3.697,7.339,1.081,10.551c-2.615,3.212-7.339,3.697-10.551,1.081
		c-8.213-6.686-19.05-8.518-28.988-4.901c-12.1,4.404-19.965,16.002-19.572,28.86c0.126,4.14-3.127,7.599-7.268,7.726
		C147.334,237.837,147.256,237.838,147.178,237.838z&quot;&gt;&lt;/path&gt;
	&lt;path style=&quot;fill:#8BCDD8;&quot; d=&quot;M407.844,221.921c-0.804,0-1.622-0.13-2.426-0.405c-3.92-1.339-6.011-5.603-4.672-9.523
		c4.547-13.308,1.2-27.747-8.737-37.684c-10.634-10.634-26.434-13.677-40.253-7.749c-3.806,1.632-8.216-0.129-9.849-3.937
		c-1.632-3.807,0.13-8.216,3.937-9.849c19.491-8.358,41.775-4.068,56.772,10.929c14.015,14.014,18.737,34.376,12.325,53.141
		C413.875,219.96,410.963,221.921,407.844,221.921z&quot;&gt;&lt;/path&gt;
	&lt;path style=&quot;fill:#8BCDD8;&quot; d=&quot;M208.844,132.131c-0.735,0-1.481-0.108-2.22-0.337c-3.957-1.225-6.172-5.425-4.947-9.382
		c3.516-11.36,13.872-18.992,25.771-18.992c10.864,0,20.62,6.466,24.852,16.473c1.613,3.815-0.171,8.215-3.986,9.829
		c-3.813,1.613-8.215-0.171-9.829-3.986c-1.879-4.444-6.212-7.316-11.037-7.316c-5.284,0-9.882,3.386-11.442,8.427
		C215.011,130.065,212.045,132.131,208.844,132.131z&quot;&gt;&lt;/path&gt;
&lt;/g&gt;
&lt;path class=&quot;drop1&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M91.734,369.476c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.03c1.417-3.893,5.72-5.898,9.613-4.483c3.892,1.417,5.899,5.721,4.482,9.613L98.782,364.54
	C97.674,367.584,94.798,369.476,91.734,369.476z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop2&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M174.454,369.181c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.03c1.417-3.893,5.721-5.899,9.613-4.483c3.892,1.417,5.899,5.721,4.482,9.613l-13.842,38.031
	C180.394,367.289,177.518,369.181,174.454,369.181z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop1&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M257.174,368.886c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.031c1.417-3.893,5.721-5.901,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C263.113,366.995,260.237,368.886,257.174,368.886z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop4&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M339.894,368.591c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.031c1.417-3.893,5.72-5.9,9.613-4.483s5.899,5.721,4.483,9.613l-13.842,38.031
	C345.833,366.7,342.957,368.591,339.894,368.591z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop3&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M422.613,368.295c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.031c1.417-3.893,5.721-5.901,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C428.553,366.404,425.677,368.295,422.613,368.295z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop1&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M113,438.073c-0.852,0-1.718-0.146-2.565-0.454c-3.892-1.417-5.899-5.721-4.483-9.613l13.842-38.031
	c1.417-3.893,5.72-5.899,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C118.939,436.182,116.063,438.073,113,438.073z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop4&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M195.72,437.778c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.031c1.417-3.893,5.719-5.9,9.613-4.483c3.892,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C201.659,435.887,198.783,437.778,195.72,437.778z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop3&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M278.439,437.482c-0.852,0-1.718-0.146-2.565-0.454c-3.892-1.417-5.899-5.721-4.482-9.613
	l13.842-38.03c1.417-3.893,5.722-5.898,9.613-4.483c3.892,1.417,5.899,5.721,4.482,9.613l-13.842,38.03
	C284.379,435.591,281.502,437.482,278.439,437.482z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop1&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M361.159,437.187c-0.852,0-1.718-0.146-2.565-0.454c-3.892-1.417-5.899-5.721-4.482-9.613
	l13.842-38.03c1.417-3.892,5.723-5.899,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C367.099,435.296,364.222,437.187,361.159,437.187z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop2&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M141.586,506.643c-0.852,0-1.718-0.146-2.565-0.454c-3.892-1.417-5.899-5.721-4.482-9.613
	l13.842-38.03c1.417-3.893,5.723-5.898,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C147.526,504.752,144.65,506.643,141.586,506.643z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop1&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M224.306,506.349c-0.852,0-1.718-0.146-2.565-0.454c-3.892-1.417-5.899-5.721-4.482-9.613
	l13.842-38.03c1.417-3.893,5.721-5.897,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C230.246,504.457,227.37,506.349,224.306,506.349z&quot;&gt;&lt;/path&gt;
&lt;path class=&quot;drop4&quot; style=&quot;fill:#8BCDD8;&quot; d=&quot;M307.026,506.054c-0.852,0-1.718-0.146-2.564-0.454c-3.893-1.417-5.899-5.721-4.483-9.613
	l13.842-38.031c1.417-3.893,5.72-5.901,9.613-4.483c3.893,1.417,5.899,5.721,4.483,9.613l-13.842,38.031
	C312.965,504.163,310.089,506.054,307.026,506.054z&quot;&gt;&lt;/path&gt;
&lt;/svg&gt;
&lt;div style=&quot;font-size: 7.5px; opacity: .6; margin-top: 1em&quot;&gt;Cloud icon by &lt;a href=&quot;http://www.freepik.com/&quot; title=&quot;Freepik&quot;&gt;Freepik&lt;/a&gt; from &lt;a href=&quot;https://www.flaticon.com/&quot; title=&quot;Flaticon&quot;&gt;flaticon.com&lt;/a&gt; is licensed by &lt;a href=&quot;http://creativecommons.org/licenses/by/3.0/&quot; title=&quot;Creative Commons BY 3.0&quot; target=&quot;\_blank&quot; data-id=&quot;2\_&quot;&gt;CC 3.0 BY&lt;/a&gt;&lt;/div&gt;
&amp;nbsp;
&lt;/div&gt;
&lt;p&gt;That’s it — stay dry!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Typing animation with JavaScript</title>
    <link href="https://eszter.space/typing-animation/"/>
    <updated>2018-06-16T00:00:00+00:00</updated>
    <id>https://eszter.space/typing-animation/</id>
    <content type="html">&lt;p&gt;Let’s imagine I’d like to show a one-line introduction on my website, but can’t decide what to say about myself. So I’d like to rotate between the phrases &lt;em&gt;I’m a web developer&lt;/em&gt;, &lt;em&gt;I like coffee&lt;/em&gt;, and &lt;em&gt;I write about stuff I’m no expert in&lt;/em&gt;. I could grab the code from the internet, but I’d like to challenge myself today and come up with a solution on my own — which is also a good way to prove it’s not hard at all!&lt;/p&gt;
&lt;p&gt;Let us begin.&lt;/p&gt;
&lt;h3&gt;Part one: rotating phrases&lt;/h3&gt;
&lt;p&gt;First, we need a default phrase, so that it works for people who have JavaScript off.&lt;/p&gt;
&lt;h5&gt;HTML&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;I&amp;#x27;m a web developer&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we will need JS to replace this nice phrase with the others. I will start by simply alternating the phrases with a &lt;code&gt;setInterval&lt;/code&gt; function.&lt;/p&gt;
&lt;h5&gt;JS&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; typeElement = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;type&amp;#x27;&lt;/span&gt;);

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; phrases = [
	&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;I&amp;#x27;m a web developer&amp;quot;&lt;/span&gt;,
	&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;I like coffee&amp;quot;&lt;/span&gt;,
	&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;I write about stuff I&amp;#x27;m no expert in&amp;quot;&lt;/span&gt;,
	&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;But then everyone else does so I guess that&amp;#x27;s ok&amp;quot;&lt;/span&gt;,
];

&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; currentIdx = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;

&lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.setInterval(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  currentIdx++;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (currentIdx &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;) {currentIdx = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;}
  typeElement.innerText = phrases[currentIdx];
}, &lt;span class=&quot;hljs-number&quot;&gt;2400&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Part two: typing&lt;/h3&gt;
&lt;p&gt;Let’s start off with a full sentence, in case someone has JS turned off, delete from there, and start typing again when we have nothing left.&lt;/p&gt;
&lt;h5&gt;HTML&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;sentence&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;example&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Type this.&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we will need to handle two directions: deleting or typing text. Once we know which way we are going, and where our cursor is (which we decide based on its previous position and the direction), we know how much text to show.&lt;/p&gt;
&lt;h5&gt;JS&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; sentenceElement = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;sentence&amp;#x27;&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; sentence = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Type this.&amp;#x27;&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; cursorPos = sentence.length;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; direction = -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; switchDirection = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; direction *= -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;

&lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.setInterval(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; atStart = cursorPos === &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; atEnd = cursorPos === sentence.length;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (atStart &amp;amp;&amp;amp; direction === -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt; || atEnd &amp;amp;&amp;amp; direction === &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;) {switchDirection();}
  cursorPos += direction;
  &lt;span class=&quot;hljs-comment&quot;&gt;/* Display only part of the text */&lt;/span&gt;
  sentenceElement.innerText = sentence.slice(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, cursorPos);
}, &lt;span class=&quot;hljs-number&quot;&gt;180&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Part three: a blinking cursor&lt;/h3&gt;
&lt;p&gt;A blinking cursor is not hard to get in pure css, we just need a good animation-timing-function. (&lt;a href=&quot;https://eszter.space/blog/2018/05/10/svg-animations.html&quot;&gt;By the way, here’s a recap on animations&lt;/a&gt; — it’s for SVGs, but can be applied to any element).&lt;/p&gt;
&lt;h5&gt;HTML&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;blink&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;|&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;CSS&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.blink&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;animation&lt;/span&gt;: blink &lt;span class=&quot;hljs-number&quot;&gt;1.1s&lt;/span&gt; linear infinite alternate;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;@keyframes&lt;/span&gt; blink {
  &lt;span class=&quot;hljs-selector-tag&quot;&gt;from&lt;/span&gt; {&lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;}
  20% {&lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;}
  28% {&lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;}
  72% {&lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;}
  80% {&lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;}
  &lt;span class=&quot;hljs-selector-tag&quot;&gt;to&lt;/span&gt; {&lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Putting it all together&lt;/h3&gt;
&lt;p&gt;The desired outcome is to show the first phrase, delete it, type the second one, delete, type etc. and start again after we have typed them all. For this, the only additional info we need is to know when to switch sentences, and this is when we are in &lt;code&gt;delete&lt;/code&gt; mode (= &lt;code&gt;direction&lt;/code&gt; is &lt;code&gt;-1&lt;/code&gt;), at &lt;code&gt;cursorPos&lt;/code&gt; 0. We can even skip a few rounds here, blink the cursor a few times, and continue with the next phrase.&lt;/p&gt;
&lt;small&gt;
Note for the cursor&#39;s blinking: the number of intervals we spend without typing, and with a blinking cursor, will not be the number of blinks, rather the number of 180ms intervals passing. There&#39;s a bit of math to get it right, the cursor blink animation timing should be divisible by the interval, without remainder. In my example, the cursor is set to blink every `1080ms`, the interval is `90ms`, and I wait `12` interval cycles, which is `12 * 90 = 1080ms`, enough for exactly
one blink.
&lt;/small&gt;
&lt;h5&gt;Here’s the code:&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; phrases = [
  &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I’m a web developer&amp;#x27;&lt;/span&gt;,
  &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I like coffee&amp;#x27;&lt;/span&gt;,
  &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;I write about stuff I’m no expert in&amp;#x27;&lt;/span&gt;,
  &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;But then everyone else does so I guess that’s ok&amp;#x27;&lt;/span&gt;,
];

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; element = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;typing-example&amp;#x27;&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; cursor = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;cursor&amp;#x27;&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; direction = -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; phraseIdx = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; sentence = phrases[phraseIdx];
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; cursorPos = sentence.length;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; blinking = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; blinkIntervals = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; switchDirection = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  direction *= -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
  toggleTyping();
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (direction === &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;) {
    &lt;span class=&quot;hljs-comment&quot;&gt;/* Just started typing again, time to switch phrase */&lt;/span&gt;
    phraseIdx++;
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (phraseIdx &amp;gt;= phrases.length) {phraseIdx = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;}
    sentence = phrases[phraseIdx];
  }
};

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; toggleTyping = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  blinking = !blinking;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (blinking) {cursor.classList.add(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;blink&amp;#x27;&lt;/span&gt;)} &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {cursor.classList.remove(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;blink&amp;#x27;&lt;/span&gt;);}
}

&lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.setInterval(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; atStart = cursorPos === &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; atEnd = cursorPos === sentence.length;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (atStart &amp;amp;&amp;amp; direction === -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt; || atEnd &amp;amp;&amp;amp; direction === &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;) {switchDirection();}

  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!blinking) {
    cursorPos += direction;
    element.innerText = sentence.slice(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, cursorPos);
  } &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
    blinkIntervals++;
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (blinkIntervals &amp;gt;= &lt;span class=&quot;hljs-number&quot;&gt;12&lt;/span&gt;) {
      blinkIntervals = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
      toggleTyping();
    }
  }
}, &lt;span class=&quot;hljs-number&quot;&gt;90&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Ways to improve loading speed</title>
    <link href="https://eszter.space/loading-speed/"/>
    <updated>2018-06-17T00:00:00+00:00</updated>
    <id>https://eszter.space/loading-speed/</id>
    <content type="html">&lt;p&gt;People hate to wait. More than that, they don’t wait, if your website doesn’t show anything nice or useful in a few seconds, they just close it. There’s plenty other websites out there that load &lt;em&gt;a little bit&lt;/em&gt; faster. So before all the images are sharp, animations smooth, fonts reflecting the most recent trends, there’s one important thing to do: make sure it is real fast. Every millisecond counts.&lt;/p&gt;
&lt;h2 id=&quot;why-is-my-website-slow&quot;&gt;Why is my website slow? &lt;a class=&quot;header-anchor&quot; href=&quot;#why-is-my-website-slow&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before we can speed up anything, we have to understand how websites are loaded in the browser.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;document&lt;/code&gt; is requested (this is usually your &lt;code&gt;index.html&lt;/code&gt; file)&lt;/li&gt;
&lt;li&gt;any resources requested by given &lt;code&gt;document&lt;/code&gt; are loaded in order of appearance, unless otherwise specified.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To better understand this process, open the dev toolbar’s network tab in Chrome (or your preferred browser), navigate to any website (&lt;a href=&quot;http://github.com/&quot;&gt;github.com&lt;/a&gt; is a good example), and examine what happens there. The key part is the waterfall (those colourful bars on the right side). When you hover them, you can see every detail of each resource, and a &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#timing-explanation&quot;&gt;link to Google’s explanation&lt;/a&gt; of what this all means.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/waterfall_detail.png&quot; alt=&quot;screenshot of the network tab waterfall of resources in detail&quot;&gt;&lt;/p&gt;
&lt;p&gt;A simplified explanation of what happens here: the first resource is &lt;strong&gt;queued&lt;/strong&gt; at 0, which is when the first &lt;code&gt;GET&lt;/code&gt; request is sent. It doesn’t spend much time in the queue or stalled, as this is the first request. It has to find the IP address to the resource (via a proxy if there is one). Once it gets to the server, the server prepares the response — this is the &lt;strong&gt;waiting&lt;/strong&gt; stage. And finally, the response &lt;strong&gt;content is downloaded&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At this point, the browser doesn’t know what’s inside that &lt;code&gt;document&lt;/code&gt;, so it can’t make any other requests until its content is downloaded.&lt;/p&gt;
&lt;p&gt;Once the client has access to that content, it can start requesting all the resources in there (scripts, images, fonts, stylesheets etc.) The browser does this in order of appearance.&lt;/p&gt;
&lt;p&gt;Let’s have a look at the bigger picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/waterfall.png&quot; alt=&quot;screenshot of the network tab&#39;s waterfall of resources&quot;&gt;&lt;/p&gt;
&lt;p&gt;The bars corresponding to these resources have mostly four visible parts: white means they are queueing, grey means they are stalled, green means the response is being prepared by the server, and blue corresponds to the time it takes for the resource to be fully downloaded from the server.&lt;/p&gt;
&lt;p&gt;The first notable thing is that the lower we go, the more time resources spend in the queue. This is because even though they were requested roughly the same time, &lt;code&gt;HTTP/1.x&lt;/code&gt; limits the number of parallel requests. That’s 6 requests in most modern browsers, but usually less in mobile browsers (4 in iOS Safari, for instance).&lt;/p&gt;
&lt;p&gt;This should no longer be an issue with the release of &lt;code&gt;HTTP/2&lt;/code&gt;, but whether that’s supported depends on both the client and the server. Most browsers support &lt;code&gt;HTTP/2&lt;/code&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, but less than one third of the websites use it &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;, so queueing is something we should worry about, for now.&lt;/p&gt;
&lt;h2 id=&quot;how-to-solve-queueing&quot;&gt;How to solve queueing? &lt;a class=&quot;header-anchor&quot; href=&quot;#how-to-solve-queueing&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, we don’t really have to &lt;em&gt;solve&lt;/em&gt; it, just handle it strategically.&lt;/p&gt;
&lt;h3&gt;Decrease the number of assets requested&lt;/h3&gt;
&lt;p&gt;This will reduce the number of assets that each request has to wait for, thus the request can start earlier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bundle &lt;code&gt;js&lt;/code&gt; files and stylesheets&lt;/li&gt;
&lt;li&gt;import dependencies through js before bundling, rather than loading scripts from the &lt;code&gt;html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;use sprites for smaller assets (icons etc.), especially if there’s many of them&lt;/li&gt;
&lt;li&gt;consider loading small SVGs inline, instead of using them as the source of an &lt;code&gt;img&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Decrease asset sizes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;minify &lt;code&gt;js&lt;/code&gt; and &lt;code&gt;css&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;use minified versions of vendor scripts, if you have to include them in the &lt;code&gt;html&lt;/code&gt; rather than importing and bundling together with your javascript. These are the ones ending in &lt;code&gt;.min.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gzip&lt;/code&gt; HTML&lt;/li&gt;
&lt;li&gt;make sure images are a suitable format (simple shapes could be inline SVGs, but more complex stuff is more efficient in PNG, JPG or similar&lt;/li&gt;
&lt;li&gt;ask the designer for optimised images :)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Use a CDN (content delivery network)&lt;/h3&gt;
&lt;p&gt;This means static content can be served from not one, but many, geographically distributed locations. The distance the request and response have to travel is smaller, therefore content is loaded faster.&lt;/p&gt;
&lt;h3&gt;Time requests strategically&lt;/h3&gt;
&lt;p&gt;In React, I’m using a custom &lt;code&gt;Image&lt;/code&gt; component that has a &lt;code&gt;delay&lt;/code&gt; property, which, using a simple &lt;code&gt;setTimeout&lt;/code&gt;, only loads after that delay has passed. Here’s the simplified version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Image&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;React&lt;/span&gt;.&lt;span class=&quot;hljs-title&quot;&gt;Component&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;props&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-built_in&quot;&gt;super&lt;/span&gt;(props);
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.state = {&lt;span class=&quot;hljs-attr&quot;&gt;loaded&lt;/span&gt;: !props.delay};
  }

  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;componentDidMount&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.timerID = &lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
      &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.setState({&lt;span class=&quot;hljs-attr&quot;&gt;loaded&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;});
    }, &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.props.delay);
  }

  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;render&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{this.state.loaded&lt;/span&gt; ? &lt;span class=&quot;hljs-attr&quot;&gt;this.props.src&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;null&lt;/span&gt;} /&amp;gt;&lt;/span&gt;&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is used as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;Image&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/assets/fluffy_the_cat.png&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;delay&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{200}&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works because in the &lt;code&gt;html&lt;/code&gt;, this is rendered as an &lt;code&gt;img&lt;/code&gt; with &lt;code&gt;src&lt;/code&gt; set to &lt;code&gt;null&lt;/code&gt; in the beginning (well, after the bundled script is loaded, of course), and only once those 200ms have passed, does it request the resource.&lt;/p&gt;
&lt;p&gt;A vanilla js solution could look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;fluffy_the_cat&amp;quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where the source is loaded once the timeout is complete.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.setTimeout({
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; catImage = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;fluffy_the_cat&amp;#x27;&lt;/span&gt;);
  catImage.src = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/assets/fluffy_the_cat.png&amp;#x27;&lt;/span&gt;;
}, &lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might say lazy loading images belongs here too, but that’s more of a visual thing, so I will leave that for next time.&lt;/p&gt;
&lt;p&gt;If you only do one thing to improve site performance, decrease the number of requests, that’s a real bottleneck. Asset size in 2018 is not the biggest issue.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://caniuse.com/#feat=http2&quot;&gt;HTTP/2 support on caniuse.com&lt;/a&gt; &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://w3techs.com/technologies/details/ce-http2/all/all&quot;&gt;Usage Statistics of HTTP/2 for Websites, August 2018&lt;/a&gt; &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Lazy loading images</title>
    <link href="https://eszter.space/lazy-loading/"/>
    <updated>2018-08-14T00:00:00+00:00</updated>
    <id>https://eszter.space/lazy-loading/</id>
    <content type="html">&lt;p&gt;In my post about loading speed, I promised to write about how I lazy load images. It is a useful technique for when you have large photos above the fold, that would take noticeable time to load. The solution is quite simple: we can make use of CSS’s &lt;code&gt;blur&lt;/code&gt; filter, with the help of &lt;code&gt;opacity&lt;/code&gt;. The basic sequence is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;load page with a tiny, blurred version of the image, and on top of it, a transparent, empty placeholder for the full size image&lt;/li&gt;
&lt;li&gt;create an &lt;code&gt;Image&lt;/code&gt; element with the full size &lt;code&gt;src&lt;/code&gt; using JavaScript&lt;/li&gt;
&lt;li&gt;when the full size asset is loaded, set it as the &lt;code&gt;src&lt;/code&gt; of the full size placeholder&lt;/li&gt;
&lt;li&gt;set the opacity of the full size image to 1.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This will look as if the same image becomes sharper, like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/goat.gif&quot; alt=&quot;photo of a goat lazy-loaded, with blur-up effect&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using a preload image means that in this specific case, my initial image size is just 7kB instead of around 500kB. The downside is that you have to know the exact width and height of the image you want to load.&lt;/p&gt;
&lt;h2 id=&quot;lets-get-to-the-code&quot;&gt;Let’s get to the code! &lt;a class=&quot;header-anchor&quot; href=&quot;#lets-get-to-the-code&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I will use React for this example. It’s very much simplified, I’m not passing classes, alt, support for any image extension etc., but you should do that in production.&lt;/p&gt;
&lt;p&gt;Our element will accept a &lt;code&gt;src&lt;/code&gt; and a &lt;code&gt;preloadSrc&lt;/code&gt; for now. Please note that it should have a fixed width and height to work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;LazyImage src=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;goat.jpg&amp;quot;&lt;/span&gt; preloadSrc=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;goat_tiny.jpg&amp;quot;&lt;/span&gt; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the component itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;LazyImage&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;React&lt;/span&gt;.&lt;span class=&quot;hljs-title&quot;&gt;Component&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;props&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-built_in&quot;&gt;super&lt;/span&gt;(props);
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.state = {&lt;span class=&quot;hljs-attr&quot;&gt;loaded&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;};
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.fullSizeImage = &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;
  }

  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;componentDidMount&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; loaderImage = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Image();
    loaderImage.src = &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.props.src;
    loaderImage.onload = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
      &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.fullSizeImage.src = src;
      &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.fullSizeImage.classList.add(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;loaded&amp;#x27;&lt;/span&gt;);
      &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.setState({&lt;span class=&quot;hljs-attr&quot;&gt;loaded&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;});
    }
  }

  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;render&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
      &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;lazy-image&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{this.props.style}&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;image-full&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;ref&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{id&lt;/span&gt; =&amp;gt;&lt;/span&gt; this.fullSizeImage = id} /&amp;gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;image-preload&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{this.props.preloadSrc}&lt;/span&gt; /&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, the CSS needed to make it work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.lazy-image&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;position&lt;/span&gt;: relative;
  &lt;span class=&quot;hljs-attribute&quot;&gt;overflow&lt;/span&gt;: hidden;

  &lt;span class=&quot;hljs-selector-class&quot;&gt;.image-full&lt;/span&gt;,
  &lt;span class=&quot;hljs-selector-class&quot;&gt;.image-preload&lt;/span&gt; {
    &lt;span class=&quot;hljs-attribute&quot;&gt;position&lt;/span&gt;: absolute;
    &lt;span class=&quot;hljs-attribute&quot;&gt;top&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
    &lt;span class=&quot;hljs-attribute&quot;&gt;left&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  }

  &lt;span class=&quot;hljs-selector-class&quot;&gt;.image-preload&lt;/span&gt; {
    &lt;span class=&quot;hljs-attribute&quot;&gt;filter&lt;/span&gt;: blur(&lt;span class=&quot;hljs-number&quot;&gt;4px&lt;/span&gt;);
  }

  &lt;span class=&quot;hljs-selector-class&quot;&gt;.image-full&lt;/span&gt; {
    &lt;span class=&quot;hljs-attribute&quot;&gt;z-index&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;;
    &lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
    transiton: opacity .&lt;span class=&quot;hljs-number&quot;&gt;6s&lt;/span&gt; ease;

    &amp;amp;&lt;span class=&quot;hljs-selector-class&quot;&gt;.loaded&lt;/span&gt; {
      &lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it, you’re as good at image loading as Medium now.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Building a reading progress bar</title>
    <link href="https://eszter.space/progress-bar/"/>
    <updated>2018-09-21T00:00:00+00:00</updated>
    <id>https://eszter.space/progress-bar/</id>
    <content type="html">&lt;p&gt;For this post, I will start from zero and experiment my way through the problem, to show it’s not that hard to build impressive animations. A reading progress bar this time.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;My initial hunch is that it has to do with scroll position and the blog post’s height. Quite useful in this case if blog posts sit in their own nice semantic &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; elements, so we don’t have to rely on the height of the full &lt;code&gt;body&lt;/code&gt; and account for the height of whatever is below the blog post itself. Our progress bar should show the exact reading position of the post itself, excluding related articles and tall footers!&lt;/p&gt;
&lt;h2 id=&quot;letʼs-do-some-maths&quot;&gt;Letʼs do some maths &lt;a class=&quot;header-anchor&quot; href=&quot;#letʼs-do-some-maths&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So we want to project our vertical scroll position onto a horizontal bar ranging from 0 to 100%. 100% means we have reached the end of the blog post, and its bottom edge is in view. Letʼs see what we know, and how can we use it. &lt;code&gt;element&lt;/code&gt; will be our blog post element.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;window.innerHeight&lt;/code&gt;: our window size&lt;/li&gt;
&lt;li&gt;&lt;code&gt;document.body.clientHeight&lt;/code&gt;: the body height&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.scrollY&lt;/code&gt;: how many pixels we have scrolled&lt;/li&gt;
&lt;li&gt;&lt;code&gt;element.offsetTop&lt;/code&gt;: where our blog post starts&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;element.clientHeight&lt;/code&gt;: the blog post element height&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The actual scroll trajectory to measure starts from the bottom of the non-scrolled page (because that is the bottom-most thing we can see without scrolling), and ends at the bottom of the element. The distance between these two points, &lt;code&gt;(element.clientHeight + element.offsetTop) - window.innerHeight&lt;/code&gt;, is equal to 100% of the scroll distance.&lt;/p&gt;
&lt;p&gt;We only need to know how much of this distance we have covered (or, how much we have scrolled so far) to show the actual progress.&lt;/p&gt;
&lt;h2 id=&quot;turning-it-into-code&quot;&gt;Turning it into code &lt;a class=&quot;header-anchor&quot; href=&quot;#turning-it-into-code&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One thing I have noticed is that in order to measure progress accurately, we should wait for all the elements to be loaded, so we will do everything on load. Once this is done, we just need a fixed &lt;code&gt;div&lt;/code&gt; that sits at the top edge of our window and has a bright background. It should start with 0% width, which we will update as we scroll.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;progress-bar&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-id&quot;&gt;#progress-bar&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;width&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0%&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;position&lt;/span&gt;: fixed;
  &lt;span class=&quot;hljs-attribute&quot;&gt;top&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;left&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;height&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;6px&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;background&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;linear-gradient&lt;/span&gt;(to right, #ecd2fe, #feaaaa);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.onload = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; post = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementsByTagName(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;article&amp;#x27;&lt;/span&gt;)[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;];
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; progressBar = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;progress-bar&amp;#x27;&lt;/span&gt;);
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; distance = (post.clientHeight + post.offsetTop) - &lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.innerHeight;

  &lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.addEventListener(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;scroll&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; progress = &lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.scrollY / distance * &lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;;
    progressBar.style.width = &lt;span class=&quot;hljs-string&quot;&gt;`&lt;span class=&quot;hljs-subst&quot;&gt;${progress}&lt;/span&gt;%`&lt;/span&gt;;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Careful though, this is calculated relative to the &lt;em&gt;closest relatively positioned parent element&lt;/em&gt; — see &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop&quot;&gt;HTMLElement.offsetTop on MDN web docs&lt;/a&gt; &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Unit testing abstract classes in React</title>
    <link href="https://eszter.space/testing-abstracts/"/>
    <updated>2018-11-06T00:00:00+00:00</updated>
    <id>https://eszter.space/testing-abstracts/</id>
    <content type="html">&lt;p&gt;In unit testing with Jest and Enzyme, a shallow render is a good way to test whether a component works like it should. But with abstract classes&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; that don’t have a &lt;code&gt;render()&lt;/code&gt; method (like one I’m trying to test right now), this fails. So how can we test abstracts?&lt;/p&gt;
&lt;h2 id=&quot;a-simple-abstract-class&quot;&gt;A simple abstract class &lt;a class=&quot;header-anchor&quot; href=&quot;#a-simple-abstract-class&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our abstract may look something like this calculator. For the sake of simplicity, it can only do one operation (but that, it can do very well!)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;abstract &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AbstractCalculator&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;React&lt;/span&gt;.&lt;span class=&quot;hljs-title&quot;&gt;Component&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;props&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-built_in&quot;&gt;super&lt;/span&gt;(props);
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.state = {&lt;span class=&quot;hljs-attr&quot;&gt;result&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;};
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.addOne = &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.addOne.bind(&lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;);
  }

  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;addOne&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; result = &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.state.result + num;
    &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt;.setState({result});
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This class could be used as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AbstractCalculator&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;props&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-built_in&quot;&gt;super&lt;/span&gt;(props);
  }

  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;render&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;)&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
      &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;strong&lt;/span&gt;&amp;gt;&lt;/span&gt;{this.state.result}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;strong&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;onClick&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{this.props.addOne}&lt;/span&gt;&amp;gt;&lt;/span&gt; +1 &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-fun-part-testing&quot;&gt;The fun part: testing! &lt;a class=&quot;header-anchor&quot; href=&quot;#the-fun-part-testing&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Normally, a test for a React class using Jest and Enzyme would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;describe(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Calculator&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; props = {&lt;span class=&quot;hljs-comment&quot;&gt;/* add some props here */&lt;/span&gt;};
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; calc = shallow(&lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;Calculator&lt;/span&gt; {&lt;span class=&quot;hljs-attr&quot;&gt;...props&lt;/span&gt;} /&amp;gt;&lt;/span&gt;&lt;/span&gt;);

  it(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;works&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
    expect(calc).toBeTruthy();
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;This will not work for abstracts.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If we try to use any of Enzyme’s renderers, e.g. &lt;code&gt;shallow(&amp;lt;AbstractComponent {...props} /&amp;gt;)&lt;/code&gt;, we will get an error like this: &lt;code&gt;TypeError: this._instance.render is not a function&lt;/code&gt;. Indeed, it is not. But we should still be able to test the rest of &lt;code&gt;AbstractComponent&lt;/code&gt;’s methods.&lt;/p&gt;
&lt;p&gt;We could test the class &lt;code&gt;Calculator&lt;/code&gt; that implements &lt;code&gt;AbstractCalculator&lt;/code&gt;, but that would not allow for isolated testing of our abstract.&lt;/p&gt;
&lt;p&gt;So let’s approach this as testing a class, rather than a React component. Instead of shallow rendering, we just create a new instance and test its methods on this instance.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; As for the props, they are, in essence, the parameters of the constructor function for the class, so we will just pass them as params when creating the test instance.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; AbstractCalculator &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;path/to/AbstractCalculator&amp;#x27;&lt;/span&gt;;

describe(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;AbstractCalculator&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; props = {&lt;span class=&quot;hljs-comment&quot;&gt;/* some test props */&lt;/span&gt;};
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; calculator = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; AbstractCalculator(props);

  it(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;works&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
    expect(calculator).toBeTruthy();  
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voilà! It works.&lt;/p&gt;
&lt;p&gt;As for state changes, we will have to mock the abstract’s &lt;code&gt;setState&lt;/code&gt; method, otherwise we get &lt;code&gt;Warning: Can’t call setState on a component that is not yet mounted.&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;it(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;adds correctly&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  calculator.setState = jest.fn();
  calculator.state.result = &lt;span class=&quot;hljs-number&quot;&gt;7&lt;/span&gt;;

  calculator.addOne();

  expect(calculator.setState).toBeCalledWith({&lt;span class=&quot;hljs-attr&quot;&gt;result&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;8&lt;/span&gt;});  
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thatʼs all — to be honest, the solution was simpler and more straightforward than I had imagined. In essence, an abstract class is just a class that can be reused easily, so it behaves just like a class in TypeScript. In our oversimplified example, that is. :)&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;See the &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/classes.html&quot;&gt;TypeScript Handbook&lt;/a&gt; for more explanation of abstract classes (youʼll have to scroll down a little.) &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Note that creating an instance with &lt;code&gt;new&lt;/code&gt; from an abstract class should not be done anywhere else, as abstract classes are there for other classes to implement. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Building an API proxy with Serverless — Part 1</title>
    <link href="https://eszter.space/serverless/"/>
    <updated>2018-12-16T00:00:00+00:00</updated>
    <id>https://eszter.space/serverless/</id>
    <content type="html">&lt;p&gt;I’m back to building a weather app, this time not in Flask, but React. I wanted to skip the server part, but DarkSky is strict about CORS&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, so I had to build a proxy. Not really a front-end topic, but good to know if you work daily with backend engineers (which I do).&lt;/p&gt;
&lt;h2 id=&quot;enter-serverless&quot;&gt;Enter serverless &lt;a class=&quot;header-anchor&quot; href=&quot;#enter-serverless&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Constantly running a server just to proxy a single API call would be an overkill — &lt;a href=&quot;https://serverless.com/&quot;&gt;Serverless&lt;/a&gt; on &lt;a href=&quot;https://aws.amazon.com/lambda/&quot;&gt;AWS Lambda&lt;/a&gt; is just the perfect way to solve this&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;. Of course, serverless is only as serverless as the cloud is not physical. It still runs on a server, but it’s someone else’s (in this case, AWS).&lt;/p&gt;
&lt;p&gt;First, we’ll have to install serverless:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;yarn global add serverless
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then create our service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sls create --template aws-nodejs --path weather-service
&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; weather-service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the way, &lt;code&gt;sls&lt;/code&gt; is a shorthand for &lt;code&gt;serverless&lt;/code&gt;. Wherever you see &lt;code&gt;serverless&lt;/code&gt; in the docs, you can use &lt;code&gt;sls&lt;/code&gt; instead.
The &lt;a href=&quot;https://serverless.com/framework/docs/providers/aws/guide/quick-start/&quot;&gt;quick start guide&lt;/a&gt; suggests to deploy now, but we can skip this until we are ready, and test our proxy locally. Let’s see what we have so far:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sls invoke &lt;span class=&quot;hljs-built_in&quot;&gt;local&lt;/span&gt; -f hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we’ve done everything right (not much to break so far), the output should be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;statusCode&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;body&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{\&amp;quot;message\&amp;quot;:\&amp;quot;Go Serverless v1.0! Your function executed successfully!\&amp;quot;,\&amp;quot;input\&amp;quot;:\&amp;quot;\&amp;quot;}&amp;quot;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Later, we can test the deployed function by calling &lt;code&gt;sls invoke -f hello&lt;/code&gt; (without &lt;code&gt;local&lt;/code&gt;), but that will count against our AWS Lambda usage.&lt;/p&gt;
&lt;h2 id=&quot;lets-turn-this-into-a-darksky-api-gateway&quot;&gt;Let’s turn this into a DarkSky API gateway &lt;a class=&quot;header-anchor&quot; href=&quot;#lets-turn-this-into-a-darksky-api-gateway&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, I’d like to call my function something more meaningful, maybe &lt;code&gt;getWeather&lt;/code&gt;. For this, let’s change the function name in &lt;code&gt;serverless.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;functions:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;getWeather:&lt;/span&gt;
    &lt;span class=&quot;hljs-attr&quot;&gt;handler:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;handler.getWeather&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in &lt;code&gt;handler.js&lt;/code&gt;, &lt;code&gt;module.exports.hello&lt;/code&gt; should become:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;module&lt;/span&gt;.exports.getWeather = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; { … }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we try &lt;code&gt;sls local invoke -f hello&lt;/code&gt;, it will fail, because there is no &lt;code&gt;hello&lt;/code&gt; function. However, &lt;code&gt;sls local invoke -f getWeather&lt;/code&gt; will work like charm!&lt;/p&gt;
&lt;h2 id=&quot;ok-on-to-the-weather&quot;&gt;Ok, on to the weather! &lt;a class=&quot;header-anchor&quot; href=&quot;#ok-on-to-the-weather&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I will use the &lt;a href=&quot;https://github.com/request/request#readme&quot;&gt;request&lt;/a&gt; library for handling requests, for no particular reason, other than it’s simple and works well with serverless. There’s no magic here, just calling &lt;a href=&quot;https://darksky.net/dev/docs&quot;&gt;Dark Sky API&lt;/a&gt; with my secret key and some hard-coded coordinates (for now). Getting the API key from &lt;code&gt;process.env&lt;/code&gt; is also a temporary solution optimised for local invokes, this may change in part 2.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;#x27;use strict&amp;#x27;&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; request = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;request&amp;#x27;&lt;/span&gt;);

&lt;span class=&quot;hljs-built_in&quot;&gt;module&lt;/span&gt;.exports.getWeather = &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;event, context, callback&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; headers = {&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Access-Control-Allow-Origin&amp;#x27;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;*&amp;#x27;&lt;/span&gt;};

  request.get(
    { &lt;span class=&quot;hljs-attr&quot;&gt;url&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;`https://api.darksky.net/forecast/&lt;span class=&quot;hljs-subst&quot;&gt;${process.env.DARKSKY_KEY}&lt;/span&gt;/44,32`&lt;/span&gt; },
    &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err, res, body&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
      &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err) {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; response = { &lt;span class=&quot;hljs-attr&quot;&gt;statusCode&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;404&lt;/span&gt;, headers, &lt;span class=&quot;hljs-attr&quot;&gt;body&lt;/span&gt;: err };
        callback(&lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, response);
      } &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; response = { &lt;span class=&quot;hljs-attr&quot;&gt;statusCode&lt;/span&gt;: res.statusCode, headers, body };
        callback(&lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, response);
      }
    }
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, &lt;code&gt;sls invoke&lt;/code&gt; will return the current weather for those coordinates. You can check out the &lt;a href=&quot;https://github.com/c0derabbit/weather/tree/master/server&quot;&gt;full source on github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my next post, I will show how to deploy the function to AWS Lambda, how to handle requests, and how to create API endpoints.
Until then, and &lt;a href=&quot;https://adventofcode.com/&quot;&gt;happy holidays&lt;/a&gt;!&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://darksky.net/dev/docs/faq#cross-origin&quot;&gt;Dark Sky API:  Frequently Asked Questions&lt;/a&gt;: “If you were to make API calls from client-facing code, anyone could extract and use your API key, which would result in a bill that you’d have to pay. We disable CORS to help keep your API secret key a secret.” Fair enough. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Plus, AWS Free Tier includes 1 million monthly invokes. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Building an API proxy with Serverless — Part 2</title>
    <link href="https://eszter.space/serverless-pt2/"/>
    <updated>2019-01-10T00:00:00+00:00</updated>
    <id>https://eszter.space/serverless-pt2/</id>
    <content type="html">&lt;p&gt;In the second part, I will turn a simple Lamda function into an API endpoint. If you havenʼt, now is a good time to &lt;a href=&quot;https://eszter.space/serverless&quot;&gt;read Part 1&lt;/a&gt; first :)&lt;/p&gt;
&lt;h2 id=&quot;from-function-to-api&quot;&gt;From function to API &lt;a class=&quot;header-anchor&quot; href=&quot;#from-function-to-api&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, let’s prepare our serverless config so that it can handle HTTP requests, and will only allow those from my website.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;functions:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;getWeather:&lt;/span&gt;
    &lt;span class=&quot;hljs-string&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;hljs-attr&quot;&gt;events:&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;http:&lt;/span&gt;
          &lt;span class=&quot;hljs-attr&quot;&gt;path:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;weather&lt;/span&gt;
          &lt;span class=&quot;hljs-attr&quot;&gt;method:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;get&lt;/span&gt;
          &lt;span class=&quot;hljs-attr&quot;&gt;cors:&lt;/span&gt;
            &lt;span class=&quot;hljs-attr&quot;&gt;origins:&lt;/span&gt;
              &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;https://myweathersite.example.com&amp;#x27;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;a-side-note-about-aws-credentials&quot;&gt;A side note about AWS credentials &lt;a class=&quot;header-anchor&quot; href=&quot;#a-side-note-about-aws-credentials&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use AWS for work, so I already had some credentials configured — but I canʼt just deploy my lambda to the company AWS account, can I? So letʼs set up a new profile. This we can do by running&lt;br&gt;
&lt;code&gt;aws configure --profile notMyCompanyProfile&lt;/code&gt;, and entering security credentials as usual. Then, we can set a default &lt;code&gt;profile&lt;/code&gt; for serverless to use, in &lt;code&gt;serverless.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;provider:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;aws&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;runtime:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;nodejs8.10&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;stage:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;prod&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;profile:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;notMyCompanyProfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;deploying-the-lambda&quot;&gt;Deploying the lambda &lt;a class=&quot;header-anchor&quot; href=&quot;#deploying-the-lambda&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we can run &lt;code&gt;sls deploy --aws-profile notMyCompanyProfile&lt;/code&gt;. Yay! If we run &lt;code&gt;sls invoke -f getWeather&lt;/code&gt; (notice the absence of &lt;code&gt;local&lt;/code&gt;), it will actually run on AWS this time. We can even &lt;code&gt;curl&lt;/code&gt; the url &lt;code&gt;sls deploy&lt;/code&gt; created for us. But when I do that now, I get &lt;code&gt;403 Forbidden&lt;/code&gt;: I have to configure my DarkSky API key on AWS. Thatʼs simply done on the AWS Consoleʼs Lambda section, under my functionʼs &lt;em&gt;Environment variables&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If I do a &lt;code&gt;curl&lt;/code&gt; now, it will get the weather for me.&lt;/p&gt;
&lt;h2 id=&quot;making-the-server-a-bit-more-flexible&quot;&gt;Making the server a bit more flexible &lt;a class=&quot;header-anchor&quot; href=&quot;#making-the-server-a-bit-more-flexible&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now a weather forecast is more useful if it can tell the weather for any location in the world, not just one it is hard coded to. In practice, this will mean for now that the client can pass coordinates for which it wants to get a weather forecast. Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;module&lt;/span&gt;.exports.getWeather = &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;event, context, callback&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { lat, lon, units } = event.queryStringParameters;
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; headers = { &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Access-Control-Allow-Origin&amp;#x27;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;*&amp;#x27;&lt;/span&gt; };
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; baseUrl = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;https://api.darksky.net/forecast/&amp;#x27;&lt;/span&gt;;
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; queryParams = !!units ? &lt;span class=&quot;hljs-string&quot;&gt;`?units=&lt;span class=&quot;hljs-subst&quot;&gt;${units}&lt;/span&gt;`&lt;/span&gt; : &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;&amp;#x27;&lt;/span&gt;;

  request.get(
    { &lt;span class=&quot;hljs-attr&quot;&gt;url&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;`&lt;span class=&quot;hljs-subst&quot;&gt;${apiUrl}&lt;/span&gt;&lt;span class=&quot;hljs-subst&quot;&gt;${process.env.DARKSKY_API_KEY}&lt;/span&gt;/&lt;span class=&quot;hljs-subst&quot;&gt;${lat}&lt;/span&gt;,&lt;span class=&quot;hljs-subst&quot;&gt;${lon}&lt;/span&gt;&lt;span class=&quot;hljs-subst&quot;&gt;${queryParams}&lt;/span&gt;`&lt;/span&gt; },
    &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err, res, body&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
      &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err) {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; response = { &lt;span class=&quot;hljs-attr&quot;&gt;statusCode&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;404&lt;/span&gt;, headers, &lt;span class=&quot;hljs-attr&quot;&gt;body&lt;/span&gt;: err };
        callback(&lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, response);
      } &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; response = { &lt;span class=&quot;hljs-attr&quot;&gt;statusCode&lt;/span&gt;: res.statusCode, headers, body };
        callback(&lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, response);
      }
    }
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thatʼs it! Now, by sending a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;https://my-api.url/weather/?lat=10&amp;amp;lon=20&lt;/code&gt;, we have weather data for these coordinates.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Building a mechanical keyboard</title>
    <link href="https://eszter.space/keeb/"/>
    <updated>2019-02-02T00:00:00+00:00</updated>
    <id>https://eszter.space/keeb/</id>
    <content type="html">&lt;p&gt;A while ago, the household got a mechanical keyboard. I tried it out, and immediately knew I needed a proper keeb. Fast forward a few months, and I’m building my own, actually soldering parts together.&lt;/p&gt;
&lt;h2 id=&quot;how-it-all-started&quot;&gt;How it all started &lt;a class=&quot;header-anchor&quot; href=&quot;#how-it-all-started&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After I tried the mech board at home, I fell in love with the feeling and got myself a lovely Vortex Race 3 for the office. It looks, um, like a mechanical keyboard, so it gets comments ranging from “OMG your keyboard is so cool”, to “oh I used to have an old keyboard like that at home”, so yes, it gets noticed.&lt;/p&gt;
&lt;p&gt;It’s also lovely to type on, super quiet compared to what I had expected (Cherry MX brown switches are absolutely office-compatible). It has built-in mac &amp;amp; linux support, colemak and dvorak layouts, plus three fully programmable layers, too, so I can map my own key combinations (arrows on the home row etc.)&lt;/p&gt;
&lt;p&gt;Fast forward a few more months, having spent a week working from bed on a macbook due to a bad cold, I got repetitive strain injury (mostly due to bad posture and hand positioning). So partly due to that, partly because why stop at one keyboard, I started looking into split keyboards.&lt;/p&gt;
&lt;h2 id=&quot;why-a-split-keyboard&quot;&gt;Why a split keyboard? &lt;a class=&quot;header-anchor&quot; href=&quot;#why-a-split-keyboard&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, if you put down your hands in front of you, you will notice they sit in an angle rather than parallel to each other. Also, if you lay your hands flat on a keyboard, you will notice that your fingers don’t all rest in the exact same vertical position. This brings us to my chosen keyboard, the &lt;a href=&quot;http://keeb.io/&quot;&gt;keeb.io&lt;/a&gt; Iris, which looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1600/0*BS_98xX0tVQa8fuo.png&quot; alt=&quot;Layout of an Iris split keyboard, consisting of two halves&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;keyboard-anatomy-101&quot;&gt;Keyboard anatomy 101 &lt;a class=&quot;header-anchor&quot; href=&quot;#keyboard-anatomy-101&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most mechanical keyboards consist of a PCB (or two, if it’s a split like the Iris — PCB stands for printed circuit board), a case to protect it, key switches, most of the times a plate to hold the switches in place, and keycaps to cover the switches and make typing comfortable.&lt;/p&gt;
&lt;p&gt;They also feature a microcontroller, such as an Arduino-compatible Pro Micro, which make it possible for the board to register key presses and send them to the computer, and enables full custom programmability (not just Vortex Race-like, but really, really full.) There’s a lot more bits and bobs that are needed for the build; we’ll get to that.&lt;/p&gt;
&lt;p&gt;Split boards have a master and a slave side; the master is connected to the computer, and the two boards are connected with a TRRS jack cable. However, a pro micro is needed on the slave side as well, to enable the two sides to communicate.&lt;/p&gt;
&lt;h2 id=&quot;preparations&quot;&gt;Preparations &lt;a class=&quot;header-anchor&quot; href=&quot;#preparations&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Getting parts&lt;/h3&gt;
&lt;p&gt;I stupidly waited until my go-to EU supplier CandyKeys ran out of Iris kits, so I had to order directly from &lt;a href=&quot;http://keeb.io/&quot;&gt;Keeb.io&lt;/a&gt; in the US. Dealing with customs clearance, shipping costs, documentation and paying a high VAT&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; is not much fun, but whatever. Order from within your customs borders, whenever you can.&lt;/p&gt;
&lt;p&gt;My kit includes 2 PCBs (for the two sides), with diodes for each switch, TRRS jack sockets, a reset button, a couple of resistors, and two pro micros (the master side with USB-C, which is considered to be more reliable on the long run, meaning it doesn’t break off).&lt;/p&gt;
&lt;p&gt;I also got 56 Cherry MX Brown switches (AliExpress is a great source for getting these), clear acrylic plates, the necessary cables (a TRRS jack, NOT the same as the ones used for audio!)&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;, and some fancy keycaps (that haven’t shipped yet).&lt;/p&gt;
&lt;h3&gt;Software&lt;/h3&gt;
&lt;p&gt;Most custom and DIY boards use the &lt;a href=&quot;https://qmk.fm/&quot;&gt;QMK Firmware&lt;/a&gt;. It already has layouts for most available boards, including the Iris. Before I start soldering, I wanted to have a look at the software side of things to see what I’m up against. Following QMK’s &lt;a href=&quot;https://docs.qmk.fm/#/newbs&quot;&gt;Complete Newbs Guide&lt;/a&gt;, I installed the QMK toolbox, played around with it for a while, it seems very straightforward.&lt;/p&gt;
&lt;h3&gt;Tools&lt;/h3&gt;
&lt;p&gt;Building a keyboard usually means a lot of soldering&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;. For those unfamiliar with soldering: the goal is to connect two pieces of metal so they conduct electricity (so that, for instance, a pressed key switch can send a signal all the way to the microcontroller, through the PCB’s circuits). This is done by melting another metal to connect the two. Melting is done by a soldering iron, and the connecting metal is tin and lead solder with a low melting point (around 200℃, but the iron melts it to a higher temperature, to be sure).&lt;/p&gt;
&lt;p&gt;A minimal soldering setup has to include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Soldering iron (I’m using a Weller)&lt;/li&gt;
&lt;li&gt;Rosin-core solder (around 0.8mm diameter is best for this purpose; mine was 1mm. It usually comes as a reeled wire, and is much heavier than I thought. A 40% lead, 60% tin content is ideal for this purpose.)&lt;/li&gt;
&lt;li&gt;Flush cutter (for cutting diode legs etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, it’s good to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multimeter (to measure conductivity)&lt;/li&gt;
&lt;li&gt;Tweezers or long nose pliers (to hold small things in place)&lt;/li&gt;
&lt;li&gt;Flux (to make solder stick better)&lt;/li&gt;
&lt;li&gt;Solder sucker or wick (for corrections — actual desoldering is best done with a proper desoldering iron)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-build&quot;&gt;The build &lt;a class=&quot;header-anchor&quot; href=&quot;#the-build&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It would be exaggerating to say this was the first time I held a soldering iron, but I was definitely not confident. I got a crash course from someone more experienced&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;, desoldered and soldered 4-5 diodes on an old circuit board that had no use, and was good to go. I mosty followed the excellent and detailed &lt;a href=&quot;https://docs.keeb.io/iris-build-guide/&quot;&gt;build guide on keeb.io&lt;/a&gt;. This is how it’s done, and my experience with it.&lt;/p&gt;
&lt;p&gt;One really important thing to note is that the two halves have to be symmetrical, so I had to solder on the other side of the other half. Otherwise, I’d end up with two left or two right halves.&lt;/p&gt;
&lt;h3&gt;1. Soldering the diodes&lt;/h3&gt;
&lt;p&gt;First, the diodes had to be separated from the paper strip holding them neatly together, then bending the legs with a plier. I also tested each of them with a multimeter before soldering anything. If one of them is faulty, better not to solder it in and realise afterwards.&lt;/p&gt;
&lt;p&gt;The only tricky thing with diodes is to insert them in the correct orientation&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;. After every four or five, I tested them with a multimeter, to see if they were soldered in correctly. I had 56 diodes to solder, that meant 112 soldering points. I think I got quite good at soldering by the end.&lt;/p&gt;
&lt;h3&gt;2. Soldering the resistors&lt;/h3&gt;
&lt;p&gt;Resistors are not orientation-sensitive, and only two of them are needed. So this was an easy step.&lt;/p&gt;
&lt;h3&gt;3. Soldering the TRRS jacks and the reset switches, one on each side.&lt;/h3&gt;
&lt;p&gt;Nothing special.&lt;/p&gt;
&lt;p&gt;After this step, there’s a bunch of optional steps that I skipped, as LED backlighting does nothing for me (for now).&lt;/p&gt;
&lt;h3&gt;4. Soldering the Pro Micro header pins&lt;/h3&gt;
&lt;p&gt;Only the header pins for now. We have to make sure they are vertical, as in as close to 90° as possible, and the two rows of pins are parallell to each other. I start by soldering a few pins on each side first, and try on the pro micro to see if it fits. If not, adjust; then solder the rest. The pro micros themselves should NOT be soldered yet.&lt;/p&gt;
&lt;h3&gt;5. Soldering the switches&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/iris_in_progress.jpg&quot; alt=&quot;&quot; title=&quot;Left half with a few switches soldered on&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is a very fun part, finally our keyboard will start to look like one! To avoid soldering a faulty switch, I test each of them with a multimeter&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;. Now, the switches have to be popped into the top plate (easy to identify — this is the one with square holes in it), pushed together with the PCB (facing the non-soldered side) and soldered into place. Following common recommendation, I first soldered in the switches in the corners to make sure everything is tight and well aligned, and added the rest of them after.&lt;/p&gt;
&lt;p&gt;It’s important here to make sure that the switches sit tightly in the plate, and fit the PCB as snug as possible. When this is done, there should be no vertical space for the switches to move within the plate&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3&gt;6. Flashing the pro micros&lt;/h3&gt;
&lt;p&gt;This step should be done before soldering them, because desoldering is a pain if it turns out that the pro micro is faulty.&lt;/p&gt;
&lt;p&gt;As a reminder, we need two of them; one for each side. Only one of them will be connected to the computer though, and I chose a USB-C version for that, the Elite-C, because they are less prone to breaking off. The standard, cheaper version is fine for the slave side.&lt;/p&gt;
&lt;p&gt;Pro micros are sensitive pieces of equipment, so I made sure  I was not charged before handling them. I did this by simply touching the bare metal parts of a radiator.&lt;/p&gt;
&lt;p&gt;To flash the pro micro (meaning, format it and install a keyboard controller), first I open the QMK toolbox on my computer, then connect the pro micro, and put it in reset mode. This is achieved by shorting &lt;code&gt;GND&lt;/code&gt; and &lt;code&gt;RST&lt;/code&gt; with a pair of tweezers or a piece of wire (while the pro micro is connected). Now, QMK toolbox should recognise the pro micro.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;&lt;/sup&gt; Flashing itself is best done from the command line — I tried using the QMK toolbox GUI, but that didn’t work properly for me. The magic command is:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make iris/rev2:default:dfu&lt;/code&gt; for the Elite-C, and &lt;code&gt;make iris/rev2:default:avrdude&lt;/code&gt; for the standard pro micro.&lt;/p&gt;
&lt;p&gt;After this step, when the pro micro is unplugged and connected again, it should show up as a keyboard — even though it’s not built into one yet!&lt;/p&gt;
&lt;h3&gt;7. Soldering the pro micros&lt;/h3&gt;
&lt;p&gt;One thing to keep in mind is that the pro micro on the master side is facing down (the side with the components to the PCB), and the one on the slave side facing up. And, practically, the USB connector towards the edge of the keyboard.&lt;/p&gt;
&lt;p&gt;I was very careful at this step, because the header pins are very close to each other, so there’s not much space, and the pro micro is a sensitive piece. I didn’t want to accidentally fry any of its tiny components with the soldering iron.&lt;/p&gt;
&lt;h3&gt;8. Putting it all together: bottom plate and keycaps&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/iris_back.jpg&quot; alt=&quot;&quot; title=&quot;Left half with clear acrylic bottom plate, showcasing the Elite-C and the soldering job&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now the easy and fun part, I put on the bottom plate using the standoffs and screws that came with the plates. They came with 10mm standoffs, which I found a bit too short, so I ordered some tiny nylon washers from RS Online, until the 12mm standoffs get here from AliExpress.&lt;/p&gt;
&lt;p&gt;The initial keycaps were cheap TaiHao caps, because they shipped fast (and because I had a hard time choosing the final ones). Now the keeb is sporting EnjoyPBT Black on Whites from Kbdfans.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/iris_finished.jpg&quot; alt=&quot;&quot; title=&quot;The Iris finished, with its final keycaps (for now).&quot;&gt;&lt;/p&gt;
&lt;p&gt;Building a keyboard was a real fun day and a half, and I’m already looking forward to my next build, probably a second Iris, for the home office.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Second poor decision: I ordered the board to Hungary, where VAT is 27%, as opposed to 20% in the UK. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The only f*$%up in my build was getting the absolutely wrong cable to connect the two halves. Luckily, even in my small hometown, a computer accessories store had the right thing in stock. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Unless it’s hotswappable, meaning you can put tiny metal tubes (hotswap sockets) into the holes in the PCB where you would solder the switches etc. otherwise, so you just pop in the sockets and the switches and you’re ready to go. This only works on PCBs that already have diodes soldered on. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Thanks, dad. If your dad is not the kickass solderer mine is, you can get the same results by searching “how to solder” on YouTube. &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;On the Iris PCB, it’s easy: black end to the square hole, other end to the round hole. &lt;a href=&quot;#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Like many things in the build, this would be much easier done with three hands instead of two: two to hold each leg and each multimeter probe end together, and a third to press the switch to test. &lt;a href=&quot;#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I learned this the hard way and had to adjust a few switches after the whole thing was soldered. At least I learned some fixing, too! I didn’t have to fully desolder these switches, just heat the tin around the switch legs enough to melt it and be able to push the switch a bit further in. Btw, I totally forgot to solder one switch properly back in, so I have one switch that is connected by mere coincidence, which I should fix ASAP. &lt;a href=&quot;#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;See more on flashing the Iris here: &lt;a href=&quot;https://docs.keeb.io/flashing-firmware/&quot;&gt;Flashing Firmware - Keebio Documentation&lt;/a&gt; &lt;a href=&quot;#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Publishing an npm package</title>
    <link href="https://eszter.space/npm/"/>
    <updated>2019-03-15T00:00:00+00:00</updated>
    <id>https://eszter.space/npm/</id>
    <content type="html">&lt;p&gt;There comes a time when you need an npm package that doesnʼt exist — or just want to share something with the world. In my case, itʼs a &lt;a href=&quot;https://kyleamathews.github.io/typography.js/&quot;&gt;Typography.js&lt;/a&gt; theme. Iʼll use &lt;code&gt;yarn&lt;/code&gt; as this is my favourite package manager, but &lt;code&gt;npm&lt;/code&gt; can be used in a similar fashion.&lt;/p&gt;
&lt;h2 id=&quot;creating-a-module&quot;&gt;Creating a module &lt;a class=&quot;header-anchor&quot; href=&quot;#creating-a-module&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This part is quite simple. &lt;code&gt;yarn init&lt;/code&gt; and answer all the questions.&lt;/p&gt;
&lt;h2 id=&quot;using-npm-packages-locally&quot;&gt;Using npm packages locally &lt;a class=&quot;header-anchor&quot; href=&quot;#using-npm-packages-locally&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We should also test the package locally while developing and before publishing it to npm. This can be done by locally linking it — basically, we are letting our local yarn or npm know that this package exists, and where it lives. We also need a test project where we import it from — Iʼm using a &lt;a href=&quot;https://www.gatsbyjs.org/starters/gatsbyjs/gatsby-starter-blog/&quot;&gt;Gatsby blog starter&lt;/a&gt;, because it already uses Typography.js by default.&lt;/p&gt;
&lt;p&gt;To link the package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; path/to/package-name
yarn link
&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; path/to/test-project
yarn link package-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can develop away.&lt;/p&gt;
&lt;h2 id=&quot;production-build-spoiler-using-babel&quot;&gt;Production build (spoiler: using Babel) &lt;a class=&quot;header-anchor&quot; href=&quot;#production-build-spoiler-using-babel&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the sake of performance and compatibility, we should minify and compile our code to be ES5-compatible. The easiest way to do it is using &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;yarn add --dev @babel/cli @babel/core @babel/preset-env babel-preset-minify
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we can add a &lt;code&gt;build&lt;/code&gt; script to &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;code&gt;&amp;quot;build&amp;quot;: &amp;quot;babel src -d dist&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Letʼs not forget to point our package entry to this &lt;code&gt;dist&lt;/code&gt; folder (it can be called something else of course):&lt;br&gt;
&lt;code&gt;&amp;quot;main&amp;quot;: &amp;quot;./dist/index.js&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;ready-to-publish&quot;&gt;Ready to publish? &lt;a class=&quot;header-anchor&quot; href=&quot;#ready-to-publish&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, we have to register on npm. At least I have to, because this is my first package published there. This is conveniently done from the command line with &lt;code&gt;npm adduser&lt;/code&gt;, which asks for a username, password and public email. Then a simple email verification, and the magic command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thatʼs it!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Mocking React components with props</title>
    <link href="https://eszter.space/mock/"/>
    <updated>2019-04-08T00:00:00+00:00</updated>
    <id>https://eszter.space/mock/</id>
    <content type="html">&lt;p&gt;In many cases, particularly in &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt;, pages are wrapped in a &lt;code&gt;&amp;lt;Layout&amp;gt;&lt;/code&gt; component. Today, Iʼll describe a way to test these pages isolated from their wrapper.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;&amp;lt;Layout&amp;gt;&lt;/code&gt; component might look something like this simplified specimen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Layout&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;{ children }&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
    &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;SiteHeader&lt;/span&gt; /&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;main&lt;/span&gt;&amp;gt;&lt;/span&gt;{children}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;main&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;SiteFooter&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  );  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an example, letʼs write a simple 404 page.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;NotFound&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
    &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;Layout&lt;/span&gt;&amp;gt;&lt;/span&gt;
      Sorry, this page does not exist. :(
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;Layout&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  );  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A test for our 404 page might look like the following, using &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; and &lt;a href=&quot;https://github.com/kentcdodds/react-testing-library&quot;&gt;react-testing-library&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;describe(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Not Found page&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  it(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;shows a friendly message&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { container } = render(&lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;NotFound&lt;/span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;);

    expect(container).toHaveTextContent(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Sorry, this page does not exist&amp;#x27;&lt;/span&gt;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Letʼs imagine &lt;code&gt;Layout&lt;/code&gt; does a lot more than in our example. If we run the 404 Not Found test right now, it will fail miserably, because thereʼs a lot more going on in &lt;code&gt;Layout&lt;/code&gt; than just putting things in a &lt;code&gt;div&lt;/code&gt;. But thatʼs all we need from it in our test, so letʼs do that!&lt;/p&gt;
&lt;h2 id=&quot;mocking-the-wrapper-component&quot;&gt;Mocking the wrapper component &lt;a class=&quot;header-anchor&quot; href=&quot;#mocking-the-wrapper-component&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not much to change in our original test, we just have to mock &lt;code&gt;Layout&lt;/code&gt; so it only returns a &lt;code&gt;div&lt;/code&gt; with the &lt;code&gt;children&lt;/code&gt; passed inside. This can be easily achieved like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;jest.mock(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./path/to/layout&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;props&lt;/span&gt; =&amp;gt;&lt;/span&gt; &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; {&lt;span class=&quot;hljs-attr&quot;&gt;...props&lt;/span&gt;} /&amp;gt;&lt;/span&gt;&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thatʼs it! This will “render” in our test as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;div&amp;gt;
  Sorry, &lt;span class=&quot;hljs-built_in&quot;&gt;this&lt;/span&gt; page does not exist. :(
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a result, our test will pass happily, because we are only testing the &lt;code&gt;NotFound&lt;/code&gt; page itself, not caring about what happens in &lt;code&gt;Layout&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Friendly UIs: Let users copy with a click</title>
    <link href="https://eszter.space/copy-tooltip/"/>
    <updated>2019-07-28T00:00:00+00:00</updated>
    <id>https://eszter.space/copy-tooltip/</id>
    <content type="html">&lt;p&gt;There’s lots of subtle but very convenient features on GitHub’s UI, one of them is being able to copy stuff from an action menu. Let’s see how we can replicate that behaviour.&lt;/p&gt;
&lt;h2 id=&quot;when-is-this-useful&quot;&gt;When is this useful? &lt;a class=&quot;header-anchor&quot; href=&quot;#when-is-this-useful&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One really good use case is GitHub’s links to specific lines in files — generally places where it’s just not feasible to show all of them. (Imagine how littered it would look!)&lt;/p&gt;
&lt;p&gt;It’s also a nice touch when you have something long to copy, and it’s not really convenient to select manually. Think product codes, long links with tokens or url params, and so forth.&lt;/p&gt;
&lt;h2 id=&quot;using-the-document-api&quot;&gt;Using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document&quot;&gt;Document API&lt;/a&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;#using-the-document-api&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt; has much more to offer than &lt;code&gt;appendChild&lt;/code&gt; and &lt;code&gt;getElementById&lt;/code&gt;, just look at this looooong list, fresh from the console:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/document-api.gif&quot; alt=&quot; &quot;&gt;&lt;/p&gt;
&lt;p&gt;Some of them are familiar, but this time we’ll use one that’s a bit more obscure: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand&quot;&gt;&lt;code&gt;document.execCommand&lt;/code&gt;&lt;/a&gt;. It can manipulate editable elements (such as &lt;code&gt;input&lt;/code&gt; and anything with the attribute &lt;code&gt;contentEditable&lt;/code&gt;). &lt;code&gt;execCommand&lt;/code&gt; takes at least one argument: the name of the command we’d like the browser to execute.&lt;/p&gt;
&lt;p&gt;You can try &lt;code&gt;document.execCommand(&#39;otters&#39;)&lt;/code&gt; of course (I have), it will simply return &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;lets-write-some-code&quot;&gt;Let’s write some code! &lt;a class=&quot;header-anchor&quot; href=&quot;#lets-write-some-code&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned, &lt;code&gt;execCommand&lt;/code&gt; needs an editable element. This is because only editable elements can be selected programmatically. Let’s try with an &lt;code&gt;input&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;input&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;some_id&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;This will be on your clipboard, chicken and all 🐓&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;onclick&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;copyToClipboard()&amp;quot;&lt;/span&gt;
/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To copy this text, an editable element must be selected. This can be done by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;finding the element: &lt;code&gt;const element = document.getElementById(&#39;my-copiable-element&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;selecting it: &lt;code&gt;element.select()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;copyToClipboard&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; input = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;some_id&amp;#x27;&lt;/span&gt;);
  input.select();
	&lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.execCommand(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;copy&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There, it should work already!&lt;/p&gt;
&lt;h2 id=&quot;but-this-is-not-really-nice-yet&quot;&gt;But this is not really nice yet. &lt;a class=&quot;header-anchor&quot; href=&quot;#but-this-is-not-really-nice-yet&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use this in production, this little snippet should meet a few more requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scalable (maybe we have a thousand elements, all copiable)&lt;/li&gt;
&lt;li&gt;pretty.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Scalability&lt;/h3&gt;
&lt;p&gt;We’ll just make sure we can pass any &lt;code&gt;id&lt;/code&gt; to &lt;code&gt;copyToClipboard&lt;/code&gt;, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;copyToClipboard&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;id&lt;/span&gt;) &lt;/span&gt;{
	&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; input = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(id);
	input.select();
	&lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.execCommand(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;copy&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;del&gt;Done.&lt;/del&gt; We’ll have to change this function after the next bit, so hang tight.&lt;/p&gt;
&lt;h3&gt;Looks&lt;/h3&gt;
&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; style the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, but that’s not what we’ll do. There’s a much more elegant solution, using any HTML element of choice plus a hidden &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. Because &lt;code&gt;id&lt;/code&gt;s should be unique, we’ll make use of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes&quot;&gt;data attributes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This would almost work, but hidden elements are not selectable. So we will use a &lt;code&gt;.hidden&lt;/code&gt; class instead, only to visually hide the element.&lt;/p&gt;
&lt;p&gt;This now is our final code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;role&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;button&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;copy-trigger&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;data-copyid&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;my_uniq_id&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;onclick&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;copyToClipboard&amp;quot;&lt;/span&gt;
&amp;gt;&lt;/span&gt;
  Copy text
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;input&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;hidden-copy-input&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;my_uniq_id&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;This will be on your clipboard, chicken and all 🐓&amp;quot;&lt;/span&gt;
/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.hidden-copy-input&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;position&lt;/span&gt;: absolute;
  &lt;span class=&quot;hljs-attribute&quot;&gt;left&lt;/span&gt;: -&lt;span class=&quot;hljs-number&quot;&gt;10000px&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;width&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1px&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;height&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1px&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; copyButtons = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementsByClassName(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;copy-trigger&amp;#x27;&lt;/span&gt;);
copyButtons.forEach(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;btn&lt;/span&gt; =&amp;gt;&lt;/span&gt; btn.addEventListener(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;click&amp;#x27;&lt;/span&gt;, copyToClipboard);

&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;copyToClipboard&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;e&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; id = e.target &amp;amp;&amp;amp; e.target.dataset &amp;amp;&amp;amp; e.target.dataset.copyid;
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; input = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(id);
  input.select();
  &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.execCommand(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;copy&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! If you want to be extra fancy, you can now show a message that it’s been copied.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;hidden&amp;quot; ...&amp;gt;&lt;/code&gt; can be used to pass values in a form that we don’t want to display. However, they are by no means a secret! &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Iʼve reviewed lots of CVs recently. Hereʼs how to improve yours</title>
    <link href="https://eszter.space/cv-opinion/"/>
    <updated>2019-08-12T00:00:00+00:00</updated>
    <id>https://eszter.space/cv-opinion/</id>
    <content type="html">&lt;p&gt;A solid CV is crucial to get that first interview. It is a good summary of what you can offer, and if that’s not obvious after a few seconds, hiring managers might just discard it without reading, no matter how good you are.&lt;/p&gt;
&lt;p&gt;So what does an &lt;em&gt;ideal&lt;/em&gt; CV look like? Quite simply, it enables hiring managers to decide in 3–5 seconds whether a call with the candidate is worth their time or not. They might also refer to it during the interview.&lt;/p&gt;
&lt;p&gt;Because of the way they are used, CVs should have &lt;strong&gt;two layers&lt;/strong&gt;: one that allows the reader to deduct a few key facts in a couple of seconds, and another one that provides more details. Both formatting and content should be adapted to serve this purpose.&lt;/p&gt;
&lt;h2 id=&quot;format&quot;&gt;Format &lt;a class=&quot;header-anchor&quot; href=&quot;#format&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a ground rule, PDF CVs are best. They look the same on all devices, can be printed or shared via email easily, and keep their formatting across all operating systems. I’ve seen web-only CVs as well (a CV page on a personal or portfolio website). This is not a bad solution, although it may look like the candidate is trying too hard to be unique by &lt;em&gt;not bothering&lt;/em&gt; to prepare and update a PDF. By all means link to your online CV, but make sure there’s a PDF version
(and they match).&lt;/p&gt;
&lt;p&gt;No CV, Europass, LinkedIn only, and other formats are not really convincing.&lt;/p&gt;
&lt;h2 id=&quot;length&quot;&gt;Length &lt;a class=&quot;header-anchor&quot; href=&quot;#length&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Keep it short.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; A good CV is &lt;strong&gt;one or two pages&lt;/strong&gt;, and well structured (more on that later). Any longer than that, and the hiring manager might not read it at all. At this stage, you really just have a couple seconds of their time.&lt;/p&gt;
&lt;h2 id=&quot;aesthetics&quot;&gt;Aesthetics &lt;a class=&quot;header-anchor&quot; href=&quot;#aesthetics&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A CV should look pleasant and readable, no matter the position. This is even more important when you are a frontend developer. The essence of the job you are applying to is to make stuff on the screen look good and serve its purpose. This is your first chance to prove you are good at it. A CV is not simply an &lt;em&gt;intention&lt;/em&gt; of applying for the job, it is part of the assessment.&lt;/p&gt;
&lt;h2 id=&quot;content&quot;&gt;Content &lt;a class=&quot;header-anchor&quot; href=&quot;#content&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It’s helpful to include your &lt;strong&gt;relevant&lt;/strong&gt; work experience (from latest to earliest), with a few bullet points about responsibilities and/or key results. No need to list your experience as a shop assistant if that’s not what you are applying for — but it’s ok to mention it in one line at the end if you’d like to explain a gap between studies and developer jobs.&lt;/p&gt;
&lt;p&gt;One more thing: thereʼs no such thing as &lt;em&gt;5+ years experience&lt;/em&gt;. A company can look for someone with 5+ years, thatʼs different, but you either have 5 or 6, but not 5+. If I see 5+, I immediately know itʼs not more, because then you would write 6 or 7 or 12, right?&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot;&gt;tl;dr &lt;a class=&quot;header-anchor&quot; href=&quot;#tldr&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Make a nice one-pager PDF including your name and contact, link to GitHub or portfolio site, relevant positions with responsibilities and/or key results in bullet points, and a mention of education, and you’ll be fine. Remember: you have five seconds of the reader’s attention.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The longest Iʼve received for a mid-level frontend position was 11 pages. ELEVEN. PAGES. What on Earth can span 11 pages in a non-academic CV? &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Supporting dark mode on the web</title>
    <link href="https://eszter.space/dark-mode/"/>
    <updated>2019-11-17T00:00:00+00:00</updated>
    <id>https://eszter.space/dark-mode/</id>
    <content type="html">&lt;p&gt;Dark mode is here to stay, and many websites, including &lt;a href=&quot;https://duckduckgo.com/&quot;&gt;DuckDuckGo&lt;/a&gt;, Twitter and my personal favourite Gatsby, make sure they blend in seamlessly with the user’s UI preferences.&lt;/p&gt;
&lt;p&gt;Some have offered their own dark mode before this could have been picked up fron the OS settings (for example, &lt;a href=&quot;https://www.cryptocompare.com/&quot;&gt;CryptoCompare&lt;/a&gt; has a ‘Turn Lights On/Off’ switch in their footer).&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;But now, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-scheme&quot;&gt;there’s a media query for that&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;@media&lt;/span&gt; (&lt;span class=&quot;hljs-attribute&quot;&gt;prefers-color-scheme:&lt;/span&gt; dark) {
  &lt;span class=&quot;hljs-selector-tag&quot;&gt;background&lt;/span&gt;: &lt;span class=&quot;hljs-selector-id&quot;&gt;#222&lt;/span&gt;;
  &lt;span class=&quot;hljs-selector-tag&quot;&gt;color&lt;/span&gt;: &lt;span class=&quot;hljs-selector-id&quot;&gt;#f0f0f0&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What if I want to use this with &lt;a href=&quot;https://www.styled-components.com/docs/advanced#theming&quot;&gt;styled-components themes&lt;/a&gt;?&lt;/h3&gt;
&lt;p&gt;JavaScript can detect media queries with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia&quot;&gt;window.matchMedia&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;window&lt;/span&gt;.matchMedia(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;(prefers-color-scheme: dark)&amp;#x27;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will return something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;MediaQueryList {
  &lt;span class=&quot;hljs-attr&quot;&gt;media&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;(prefers-color-scheme: dark)&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;matches&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;, &lt;span class=&quot;hljs-comment&quot;&gt;// or false :)&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;onchange&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, we can set our styled-components theme based on whether there’s a match.&lt;/p&gt;
&lt;p&gt;If I did that right (and you have a modern-enough browser), my website should be matching your system preferences!&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I used to have one too, but one has to keep up with the changing times! &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Ps. you can still switch between light &amp;amp; dark manually, and that will be remembered in &lt;code&gt;localStorage&lt;/code&gt;. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Curious games</title>
    <link href="https://eszter.space/games/"/>
    <updated>2020-01-27T00:00:00+00:00</updated>
    <id>https://eszter.space/games/</id>
    <content type="html">&lt;p&gt;The closest I ever got to gaming was &lt;a href=&quot;http://www.oddworld.com/oddworldgames/abes-oddysee/&quot;&gt;Abe’s Oddysee&lt;/a&gt; (I guess Robot Unicorn Attack doesn’t count.) — but I do enjoy the odd game now and then.&lt;/p&gt;
&lt;p&gt;Here’s an absolutely subjective selection for killing (not too much) time.&lt;/p&gt;
&lt;h3&gt;Lunar lander&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; a simple but challenging game where you have to land a rover on the moon.&lt;br&gt;
&lt;strong&gt;How to play:&lt;/strong&gt; online on &lt;a href=&quot;http://moonlander.seb.ly/&quot;&gt;moonlander.seb.ly&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Why:&lt;/strong&gt; because it’s so pretty and monochrome.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/lunar.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;killersheep&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; sheep invaders. It’s a vim plugin demonstrating new vim features (as in, &lt;a href=&quot;https://www.vim.org/vim-8.2-released.php&quot;&gt;popups in 8.2&lt;/a&gt;).&lt;br&gt;
&lt;strong&gt;How to play:&lt;/strong&gt; by upgrading vim and installing the &lt;a href=&quot;https://github.com/vim/killersheep&quot;&gt;vim plugin&lt;/a&gt;.&lt;br&gt;
&lt;strong&gt;Why:&lt;/strong&gt; because you can shoot at neon sheep flying across your vim screen and pooping at you.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/sheep.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;ninvaders&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; space invaders in your console.&lt;br&gt;
&lt;strong&gt;How to play:&lt;/strong&gt; &lt;code&gt;brew install ninvaders&lt;/code&gt; or &lt;code&gt;sudo apt-get install ninvaders&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Why:&lt;/strong&gt; you shouldn’t have to exit the console to play space invaders.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/ninvaders.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Google console text adventure game&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; a text adventure game. I didn’t find it too interesting, but it’s an honorable mention.&lt;br&gt;
&lt;strong&gt;How to play:&lt;/strong&gt; Google ‘Text adventure’ (really &lt;em&gt;Google&lt;/em&gt;, don’t DuckDuckGo) and open the console.\&lt;br&gt;
&lt;strong&gt;Why:&lt;/strong&gt; there’s lots of creative stuff people do in the console. This is one of them.&lt;/p&gt;
&lt;h3&gt;Bonus: Chess&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; good old chess, human versus machine.&lt;br&gt;
&lt;strong&gt;How to play:&lt;/strong&gt; if you have a mac, you have chess.&lt;br&gt;
&lt;strong&gt;Why:&lt;/strong&gt; being beaten by the computer &lt;a href=&quot;https://en.wikipedia.org/wiki/Deep_Blue_versus_Kasparov,_1996,_Game_1&quot;&gt;feels a bit like being part of history&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Keeping a healthy work/life balance at home</title>
    <link href="https://eszter.space/wfh-tips/"/>
    <updated>2020-03-13T00:00:00+00:00</updated>
    <id>https://eszter.space/wfh-tips/</id>
    <content type="html">&lt;p&gt;Working full-time remote is a bit different from the occasional home office. With this ugly virus going around and forcing a lot of employees to stay home, I’d like to share a couple of tips for a healthy work/life balance and  avoiding isolation.&lt;/p&gt;
&lt;h2 id=&quot;1-separate-office-space-and-time&quot;&gt;1. Separate office space (and time) &lt;a class=&quot;header-anchor&quot; href=&quot;#1-separate-office-space-and-time&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One can occasionally work from home at the kitchen table or from the sofa, but for any more than a day or two, this just doesn’t cut it. There needs to be &lt;strong&gt;physical separation&lt;/strong&gt; of work and non-work, so we can switch off after we’ve left the office. So we can &lt;em&gt;leave&lt;/em&gt; the office. This is extremely important both to keep focused at work, and to be able to switch off after.&lt;/p&gt;
&lt;p&gt;If you have a study or guest room you can use, use it — otherwise, simply nominating a chair to be your ‘work chair’ can make a huge difference. Also, &lt;strong&gt;avoid checking anything work-related from your personal computer, and outside of working hours&lt;/strong&gt;. If you have to do work stuff at unusual hours, go to your designated office space to do it, so you &lt;em&gt;know&lt;/em&gt; you are working, not spending hours ‘just checking something quickly’.&lt;/p&gt;
&lt;p&gt;If you share your home with someone, make sure they know about your working hours and your working spot, and respect it. For me, it helps a lot that we &lt;strong&gt;say goodbye in the morning&lt;/strong&gt; before “going” to work, and &lt;strong&gt;say hello after work when we “get home”&lt;/strong&gt;. We also let each other know if we will stay at work longer than usual.&lt;/p&gt;
&lt;h2 id=&quot;2-make-sure-youre-comfortable-the-good-way&quot;&gt;2. Make sure you’re comfortable, the good way &lt;a class=&quot;header-anchor&quot; href=&quot;#2-make-sure-youre-comfortable-the-good-way&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don’t mean sitting on a couch, with the laptop in your lap. That’s a prime way for getting repetitive strain injury&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. Ask yourself: “Would this be comfy for watching Netflix?” If the answer is yes, it’s not for extended work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sit at a desk or table&lt;/strong&gt;, use cushions if your chair is not comfortable, and try to change your setup if something hurts. If you have an external keyboard, use it, and put some books under your laptop to bring the screen closer to eye level.&lt;/p&gt;
&lt;h2 id=&quot;3-stay-in-touch-with-the-team&quot;&gt;3. Stay in touch with the team &lt;a class=&quot;header-anchor&quot; href=&quot;#3-stay-in-touch-with-the-team&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Speak to people often.&lt;/strong&gt; Video calls, with the camera on, should happen ad-hoc, without requiring a calendar invite. Respect others’ focus times of course, and from your side, try to be available as much as possible.&lt;/p&gt;
&lt;p&gt;Make it clear for everyone when you are available. Don’t rely on just being online on Slack, instead,  &lt;strong&gt;say hi in the morning&lt;/strong&gt; and &lt;strong&gt;say goodbye in the afternoon&lt;/strong&gt;. No Irish goodbye! Similarly, let the team know if you’ll be unresponsive for more than 5 minutes or so (e.g. &lt;em&gt;“Need some head space, will be on dnd for an hour”&lt;/em&gt;, &lt;em&gt;“Will have lunch now”&lt;/em&gt;, or &lt;em&gt;“Off for a coffee break, back in 15”&lt;/em&gt;), and make use of Slack statuses.&lt;/p&gt;
&lt;p&gt;But the biggest loss when not sharing an office is the lack of watercooler talk. You can still do that: ask someone if they want to share a coffee break with you, pick up the phone, and just chat about whatever comes up.&lt;/p&gt;
&lt;h2 id=&quot;4-take-regular-breaks&quot;&gt;4. Take regular breaks &lt;a class=&quot;header-anchor&quot; href=&quot;#4-take-regular-breaks&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It’s easier (for me at least) to get really, really lost in work in the quiet of the home. &lt;a href=&quot;https://en.wikipedia.org/wiki/Pomodoro_Technique&quot;&gt;Pomodoros&lt;/a&gt; help because they remind me of two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I’m here to focus&lt;/li&gt;
&lt;li&gt;I need a break once in a while.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I use pomodoro to limit working time, not to limit breaks. Sometimes I skip a break, but never two. Some of my breaks are 30 seconds, some are 15 minutes. In the end, do what works for you best, just make sure you do take regular breaks.&lt;/p&gt;
&lt;h2 id=&quot;5-have-pre-and-post-work-rituals&quot;&gt;5. Have pre- and post-work rituals &lt;a class=&quot;header-anchor&quot; href=&quot;#5-have-pre-and-post-work-rituals&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Always, always put on something you would wear to work. &lt;a href=&quot;https://www.nytimes.com/2012/04/03/science/clothes-and-self-perception.html&quot;&gt;How we dress affects how we perform&lt;/a&gt;, and getting dressed prepares you for leaving home, and entering office space.&lt;/p&gt;
&lt;p&gt;Keep your regular pre-work routine as well: if you shower in the morning, keep doing it.&lt;/p&gt;
&lt;p&gt;And, of course, make sure you &lt;strong&gt;get home&lt;/strong&gt; from work. What do you usually do? Have a cup of tea, sit down in the living room? Call someone? Cook dinner? Whatever you already do, keep doing it.&lt;/p&gt;
&lt;p&gt;Use the time gained by not having to commute. Maybe you can go for a walk before work. Read a little, catch up on your painting, writing or soap making. Keep safe 😷, and keep in touch.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://medict.netlify.com/?q=repetitive%20strain%20injury&quot;&gt;RSI&lt;/a&gt; manifests as a pain in the wrist, forearms, neck or shoulders due to damaged tendons. Although it lead me to the very enjoyable hobby of &lt;a href=&quot;https://eszter.space/keeb&quot;&gt;building a keyboard&lt;/a&gt;, I would recommend trying to avoid it. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>GitHub CLI: create and manage PRs from the command line</title>
    <link href="https://eszter.space/gh-cli/"/>
    <updated>2020-04-12T00:00:00+00:00</updated>
    <id>https://eszter.space/gh-cli/</id>
    <content type="html">&lt;p&gt;Recently released in beta, &lt;a href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI&lt;/a&gt; managed to integrate seamlessly in my workflow, like it’s always been there. As &lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git&quot;&gt;oh-my-zsh’s git aliases&lt;/a&gt;, they quickly became muscle memory.&lt;/p&gt;
&lt;p&gt;Of course, there are two kinds of programmers: those who use a &lt;a href=&quot;https://desktop.github.com/&quot;&gt;desktop client for git&lt;/a&gt;, and those who don’t&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. Vim being my editor of choice, you can probably guess which camp I’m in. Hearing that GitHub have released a tool &lt;em&gt;just for me&lt;/em&gt; was music to my ears.&lt;/p&gt;
&lt;h3&gt;What is it for?&lt;/h3&gt;
&lt;p&gt;Mainly for managing pull requests and issues directly from the command line.&lt;/p&gt;
&lt;h3&gt;How to get it&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;brew install gh&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;chechking-prs-for-code-reviews-gh-pr-view-and-gh-pr-checkout&quot;&gt;Chechking PRs for code reviews: &lt;code&gt;gh pr view&lt;/code&gt; and &lt;code&gt;gh pr checkout&lt;/code&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;#chechking-prs-for-code-reviews-gh-pr-view-and-gh-pr-checkout&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a little bit of context, as a frontend developer, I like to always run pull requests locally, to make sure what I review works and looks fine (sure, QA can do that too, but let them catch the bugs that aren’t obvious.) So, reviewing a pull request involves reading the changes, checking out the branch, and running locally.&lt;/p&gt;
&lt;p&gt;In a browser-based workflow, this looks something like the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;copy the branch name from &lt;a href=&quot;http://github.com/org/repo/pull/123&quot;&gt;github.com/org/repo/pull/123&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git checkout the-pr-branch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;oh, I forgot to pull, it’s not there yet. &lt;code&gt;git pull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git checkout the-pr-branch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;run code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same using the cli:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gh pr list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gh pr checkout 123&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;run code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s my kind of workflow! ⚡️&lt;/p&gt;
&lt;h4&gt;This is what happens in a bit more detail:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gh pr list

Pull requests &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; some-org/some-repo

&lt;span class=&quot;hljs-comment&quot;&gt;#123  Add some great feature  feat/cool-feature&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;#124  Fix nasty bug           fix/nasty-bug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gh pr list&lt;/code&gt; shows me all the open pull requests in the current repo. All I need to do is choose which one I’m interested in.&lt;/p&gt;
&lt;p&gt;Now comes the best part: &lt;code&gt;checkout&lt;/code&gt; switches to the PR’s branch based on PR number.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gh pr checkout 123

Switched to branch feat/cool-feature
Your branch is up-to-date with origin/feat/cool-feature.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can also open the PR on &lt;a href=&quot;http://github.com/&quot;&gt;github.com&lt;/a&gt; right from the terminal (it opens the browser automatically):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gh pr view 123 --web

Opening https://github.com/some-org/some-repo/pull/123 &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; your browser.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;opening-pull-requests-gh-pr-create&quot;&gt;Opening pull requests: &lt;code&gt;gh pr create&lt;/code&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;#opening-pull-requests-gh-pr-create&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gh pr create

Creating pull request &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; feat/cool-feature into master &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; some-org/some-repo
? Title ▌
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After filling title and body (the latter using your system editor&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;), it will give you the option to preview, submit or cancle the pull request. Depending on the task, I usually preview and request reviews, but sometimes just submit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gh pr create

Creating pull request &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; feat/cool-feature into master &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; some-org/some-repo
? Title Fix nasty bug
? Body &amp;lt;Received&amp;gt;
? What&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;s next?  [Use arrows to move, type to filter]
&amp;gt; Preview in browser
  Submit
  Cancel
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also open a &lt;a href=&quot;https://github.blog/2019-02-14-introducing-draft-pull-requests/&quot;&gt;draft pull request&lt;/a&gt; by passing the &lt;code&gt;--draft&lt;/code&gt; (or &lt;code&gt;-d&lt;/code&gt;) flag: &lt;code&gt;gh pr create -d&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;useful-for-checks-gh-pr-status&quot;&gt;Useful for checks: &lt;code&gt;gh pr status&lt;/code&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;#useful-for-checks-gh-pr-status&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can check the overall PR status of a branch with &lt;code&gt;gh pr status&lt;/code&gt;. This is not an overview though; it is branch-specific. So for the following output, you have to be on the same branch as the PR:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gh pr status

Current branch
  &lt;span class=&quot;hljs-comment&quot;&gt;#123  Add cool feature&lt;/span&gt;
    - Checks passing
  &lt;span class=&quot;hljs-comment&quot;&gt;#124  Fix nasty bug&lt;/span&gt;
    - Checks passing

Created by you
  You have no open pull requests

Requesting a code review from you
  &lt;span class=&quot;hljs-comment&quot;&gt;#123  Add cool feature&lt;/span&gt;
    - Checks passing - Review required
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;issues-etc&quot;&gt;Issues etc. &lt;a class=&quot;header-anchor&quot; href=&quot;#issues-etc&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can list, view (open in browser) and open issues with the respective commands &lt;code&gt;gh issue list&lt;/code&gt;, &lt;code&gt;gh issue view 10&lt;/code&gt;, and &lt;code&gt;gh issue create&lt;/code&gt;. They work very similarly to &lt;code&gt;pr&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;And, just as I was checking the docs to fact-check what I’m writing, I’ve discovered &lt;a href=&quot;https://cli.github.com/manual/gh_repo&quot;&gt;you can create a repo&lt;/a&gt; with &lt;code&gt;gh repo create some-org/another-repo&lt;/code&gt;. Not something I do on a daily basis, but I might even get to using it! On the other hand, &lt;code&gt;gh repo clone some-org/some-other-repo&lt;/code&gt; is a much friendlier experience than either remembering the whole .git repo url syntax or visiting the repo and clicking clone.&lt;/p&gt;
&lt;h2 id=&quot;overall-lgtm&quot;&gt;Overall? Lgtm. &lt;a class=&quot;header-anchor&quot; href=&quot;#overall-lgtm&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GitHub CLI is a simple, easy-to-use tool that’s also easy to love.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cli.github.com/manual/&quot;&gt;Be sure to check out the docs&lt;/a&gt;, as there are a lot of useful options. Happy coding! 🎉&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I admit this may be a very personal choice, and I have an opinionated… opinion. That is, to understand git properly, and solve more complex issues, it’s better to use the command line. As the &lt;a href=&quot;https://github.com/desktop/desktop/blob/development/docs/process/what-is-desktop.md#1-github-desktop-reduces-frustration-and-makes-git-and-github-workflows-more-approachable&quot;&gt;desktop repo states&lt;/a&gt;, &lt;em&gt;“GitHub Desktop is not a replacement for the functionality of Git, but a tool to enable you and your team to be more productive.”&lt;/em&gt; &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;You can set this in &lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.bash_profile&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt;, whichever you use, by adding &lt;code&gt;export EDITOR=vim&lt;/code&gt; (or any other editor you like.) &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>CSS tricks: respecting spacing in hash navigation</title>
    <link href="https://eszter.space/hash-nav-spacing/"/>
    <updated>2020-04-28T00:00:00+00:00</updated>
    <id>https://eszter.space/hash-nav-spacing/</id>
    <content type="html">&lt;p&gt;When implementing a hash-based navigation, it’s not trivial to control the actual starting position of headings. This could cause headings to be cut off or hidden below your floating navigation. I’ll show you two ways to fix that.&lt;/p&gt;
&lt;h2 id=&quot;but-first-whats-the-problem&quot;&gt;But first: what’s the problem? &lt;a class=&quot;header-anchor&quot; href=&quot;#but-first-whats-the-problem&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When using hash navigation, browsers take advantage of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id&quot;&gt;id attribute&lt;/a&gt; to find and jump to elements on the page. This is very useful in long documents, when you’d like to enable visitors to jump to sections further down. So, for instance, you can have a &lt;code&gt;&amp;lt;h2 id=&amp;quot;monstera&amp;quot;&amp;gt;&lt;/code&gt;, and upon clicking a link &lt;code&gt;&amp;lt;a href=&amp;quot;#monstera&amp;quot;&amp;gt;&lt;/code&gt;, users are taken to the respective section. This works with any kind of HTML element – just make sure &lt;code&gt;id&lt;/code&gt;s are unique.&lt;/p&gt;
&lt;p&gt;But the problem is &lt;em&gt;where&lt;/em&gt; exactly it jumps to: right where content starts.&lt;/p&gt;
&lt;p&gt;Consider &lt;a href=&quot;https://jsfiddle.net/sgwomzv8/2/&quot;&gt;this example&lt;/a&gt;. As you navigate back and forth between the two sections, the title will be hidden by the floating header. As you can see in the screenshot below, the text inside &lt;code&gt;h2#monstera&lt;/code&gt; is right at the document’s top, with a lower z-index than the header’s, and it seemingly doesn’t respect the margin. Except, it does — margin is just not part of the content.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/c0derabbit/eszter.space/master/content/assets/hidden.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, how could we &lt;em&gt;make&lt;/em&gt; that space part of the content?&lt;/p&gt;
&lt;h2 id=&quot;simplest-solution-change-margin-to-padding&quot;&gt;Simplest solution: change margin to padding &lt;a class=&quot;header-anchor&quot; href=&quot;#simplest-solution-change-margin-to-padding&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pretty easy: change your element’s margin to 0, and add some padding. Of course, you can offset the extra padding with a negative margin, in case you want less spacing between sections.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;h2&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;margin&lt;/span&gt;: -&lt;span class=&quot;hljs-number&quot;&gt;1em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;padding&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1.8em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; .&lt;span class=&quot;hljs-number&quot;&gt;5em&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://jsfiddle.net/sgwomzv8/3/&quot;&gt;Here’s the previous demo, updated.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;alternative-solution-before&quot;&gt;Alternative solution: &lt;code&gt;::before&lt;/code&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;#alternative-solution-before&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you don’t want to change padding for some reason, you can use a the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/::before&quot;&gt;&lt;code&gt;::before&lt;/code&gt;&lt;/a&gt; pseudo-element as well. It will insert a node as the first child of our &lt;code&gt;h2&lt;/code&gt;, and as such, is part of the box. You can control its height as below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;h2&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;margin-top&lt;/span&gt;: -&lt;span class=&quot;hljs-number&quot;&gt;1em&lt;/span&gt;;
}

&lt;span class=&quot;hljs-selector-tag&quot;&gt;h2&lt;/span&gt;&lt;span class=&quot;hljs-selector-pseudo&quot;&gt;::before&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;content&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;&amp;#x27;&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;display&lt;/span&gt;: block;
  &lt;span class=&quot;hljs-attribute&quot;&gt;height&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1.8em&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://jsfiddle.net/sgwomzv8/5/&quot;&gt;Here’s the demo for this version.&lt;/a&gt;&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;To understand how this works, I recommend reading about the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model&quot;&gt;box model&lt;/a&gt;. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>The great expectations of working remotely: 3-month check-in</title>
    <link href="https://eszter.space/remote/"/>
    <updated>2020-05-09T13:12:54+00:00</updated>
    <id>https://eszter.space/remote/</id>
    <content type="html">&lt;p&gt;I’ve been looking forward to this. Not having to commute, living in a small town close to nature, still being able to contribute in an exciting startup in a meaningful way. What have I been expecting exactly, and how has it worked out, you ask?&lt;/p&gt;
&lt;p&gt;Here’s a list of all my assumptions (and, in some cases, doubts). I noted down all the expectations a couple days before I started working remotely, and after roughly 3 months, it’s time to check in.&lt;/p&gt;
&lt;h3&gt;Expectation: I will have lots of uninterrupted time to focus.&lt;/h3&gt;
&lt;p&gt;I will use &lt;a href=&quot;https://en.wikipedia.org/wiki/Pomodoro_Technique&quot;&gt;pomodoro&lt;/a&gt; and expect to be done with my 2x4 sets of pomodoros way before 5pm, as opposed to 5.30 or 6pm these days.&lt;/p&gt;
&lt;h4&gt;Reality: 😕 Haha. Well, not really.&lt;/h4&gt;
&lt;p&gt;The majority interruptions don’t come in the form of someone walking up to my desk and asking something. Instead, they tend to come in &lt;em&gt;something hijacking my attention&lt;/em&gt;, regardless the channel. As for pomodoro, I gave it a try but I struggle to keep up with it. I really should, because it helps a lot in focusing.&lt;/p&gt;
&lt;p&gt;Also, I’m not finishing work earlier. But not later either, so it’s ok.&lt;/p&gt;
&lt;h3&gt;Expectation: I will miss the watercooler talk (and social interaction in general).&lt;/h3&gt;
&lt;p&gt;To fight this, I will make an effort to get out — sometimes go to a café for my morning espresso, join a country walking club, go to the library, go to the same baker…&lt;/p&gt;
&lt;h4&gt;Reality: 🤔 ?&lt;/h4&gt;
&lt;p&gt;Oh, of course I do, but it’s a question mark because the past couple months haven’t exactly gone according to plan.&lt;/p&gt;
&lt;p&gt;First, a bit of context. It’s 2020, so if you’re reading this from the future, it’s the year when we spent months in lockdown due to Covid-19. (Sorry, I promised myself to avoid the topic in my blog, but I feel that it belongs.)&lt;/p&gt;
&lt;p&gt;The plan was to visit the office every couple months. Next week would be the second time I would have seen my colleagues since going remote, had it not been for the lockdown. But I haven’t seen any of them in person since February — and I really wouldn’t mind to do so by now! On the other hand, living in a small town means people are generally eager to spend a few minutes longer chatting about whatever. Nobody is in a hurry, and if you are open to it, any transaction (buying bread, signing up for a library card) can be more than just a transaction.&lt;/p&gt;
&lt;p&gt;Yet… I’ll have to report back on this later.&lt;/p&gt;
&lt;h3&gt;Expectation: I will be healthier.&lt;/h3&gt;
&lt;p&gt;As in, I will move a lot more. A couple push-ups before work, the occasional swim, the frequent jog, and much more walking, as I will live in a much quieter place.&lt;/p&gt;
&lt;h4&gt;Reality: 😊 A bit better physical, and much better mental health.&lt;/h4&gt;
&lt;p&gt;Sometimes, I go for a long walk on my lunch break, or hop on my bicycle right after work. Or I can go for a jog in the morning. Or a walk. Air quality is much better here too, and being a quiet small town, there’s less noise and light pollution, so I sleep much better.&lt;/p&gt;
&lt;p&gt;And something I’ve not expected or thought of: I have the time and energy to take care of myself, and I’m not nearly as stressed as I used to be in London.&lt;/p&gt;
&lt;h3&gt;Expectation: I will have the willpower to go out and have quality social interactions.&lt;/h3&gt;
&lt;p&gt;As an introvert, whatever social interaction I get from my commute and at the office is enough to put me in a state when I just want to be left alone and not talk to anyone.&lt;/p&gt;
&lt;p&gt;The problem with big city life for me is that there is a limit to what I can comfortably have in a day before I want to turn the world off. And the quality of the interaction doesn’t matter — it’s building up whether it’s a lovely after-work drink with friends or rushing past hundreds of pedestrians and dodging cyclists in central London. So by the time it’s time for quality social interactions, I can’t take any more of them.&lt;/p&gt;
&lt;p&gt;That, I hope, will change.&lt;/p&gt;
&lt;h4&gt;Reality: 😊 Pub Wednesdays!&lt;/h4&gt;
&lt;p&gt;Absolutely yes, and I’m always looking for our “Pub Wednesdays” (which sometimes fall on Tuesdays because I just can’t wait to go out.) The answer to “shall we do x tonight/this weekend?” is hardly ever a “nah, I’d rather stay in.”&lt;/p&gt;
&lt;h3&gt;Expectation: I will have two extra hours a day to do something useful.&lt;/h3&gt;
&lt;p&gt;A lot of time is lost to commute, and some to saying hello and goodbye at the office and generally being interrupted by people who arrive later and leave earlier. You are trying to concentrate, but you also want to say hi, so you step out of your focus zone and chat for a minute or two. Then back to work, but someone else arrives five minutes later, and the same thing happens again.&lt;/p&gt;
&lt;p&gt;It’s not that I don’t like saying hi and chatting for a couple of minutes, I love it! But it translates to the first twenty minutes or so at the office absolutely unproductive from a work point of view.&lt;/p&gt;
&lt;h4&gt;Reality: 😊 I hugely underestimated the difference lack of commute makes.&lt;/h4&gt;
&lt;p&gt;Now I feel like days aren’t long enough, I have much more mental energy left by the end of the day than I can execute on. I’ve started new pet projects I will never finish, picked up forgotten books, improved my self-care routine (from basically zero), started looking for new hobbies to pick up. I never go to bed exhausted (except for full-day hiking or biking, of course.)&lt;/p&gt;
&lt;h3&gt;Expectation: Finding a good system for the home office work lunch will be challenging.&lt;/h3&gt;
&lt;p&gt;I have no good ideas right now on what to do with lunch and lunch time. Cooking a proper meal every lunch seems daunting. Doing my cooking for the week beforehand and eating the same stuff every day seems boring. Going out a lot for lunch seems expensive. Quick-fixing lunch seems bland. I know I will need a system, and I’d also like to minimise decision-making on this one, but not eat anything mediocre at the same time (life is too short for that).&lt;/p&gt;
&lt;h4&gt;Reality: 🤷‍♀️ Lunch is not so important.&lt;/h4&gt;
&lt;p&gt;I never really made a big deal of lunch at the office, and I don’t do now. Sometimes we cook something. Sometimes we go for a walk and pick up a sandwich from a bakery in town. Sometimes I just have a nibble. What we &lt;em&gt;do&lt;/em&gt; do is try to meet for lunch most days.&lt;/p&gt;
&lt;p&gt;Dinner is the more important one, and because I never have a big lunch, I’m usually hungry by the time I finish work, which means an early dinner, which means better sleep.&lt;/p&gt;
&lt;h3&gt;Expectation: Keeping a daily routine will work.&lt;/h3&gt;
&lt;p&gt;I think it will! Like brushing my teeth and putting on clothes I would actually wear in civilisation, and taking breaks at regular intervals.&lt;/p&gt;
&lt;h4&gt;Reality: 😊 It does!&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://eszter.space/wfh-tips/&quot;&gt;I wrote about the importance of routine here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion? &lt;a class=&quot;header-anchor&quot; href=&quot;#conclusion&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I think I’ve made the right decision going remote, and I feel very fortunate to have a career and an employer who support it. I hope I’m not an exception but part of a tendency, and more of us will be given the choice to work remotely, each in the environment where we can best function.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Turning any website into a PWA with (mostly) vanilla JS</title>
    <link href="https://eszter.space/pwa/"/>
    <updated>2020-06-25T15:00:00+00:00</updated>
    <id>https://eszter.space/pwa/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps&quot;&gt;Progressive web apps (PWAs)&lt;/a&gt; are installable, responsive web applications that work on any device (within reason) and deliver a reliable experience on unreliable networks, and even work offline (once they’ve loaded, of course).&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;progressive&lt;/em&gt; in the name suggests that they provide an &lt;em&gt;acceptable experience&lt;/em&gt; on as many browsers as possible&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, while offering an advanced experience on modern browsers, enabling new features as they become available.&lt;/p&gt;
&lt;h2 id=&quot;pwa-availability-in-major-web-frameworks&quot;&gt;PWA availability in major web frameworks &lt;a class=&quot;header-anchor&quot; href=&quot;#pwa-availability-in-major-web-frameworks&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gatsbyjs.org/docs/progressive-web-app/&quot;&gt;Gatsby&lt;/a&gt; and &lt;a href=&quot;https://sapper.svelte.dev/docs#Deploying_service_workers&quot;&gt;Sapper&lt;/a&gt; offer it out of the box. It’s opt-in both in &lt;a href=&quot;https://create-react-app.dev/docs/making-a-progressive-web-app/&quot;&gt;React&lt;/a&gt; and &lt;a href=&quot;https://angular.io/api/service-worker&quot;&gt;Angular&lt;/a&gt;, and &lt;a href=&quot;https://cli.vuejs.org/core-plugins/pwa.html&quot;&gt;Vue offers a core plugin&lt;/a&gt;. There’s a &lt;a href=&quot;https://github.com/okitavera/eleventy-plugin-pwa&quot;&gt;PWA plugin&lt;/a&gt; for &lt;a href=&quot;https://11ty.dev/&quot;&gt;11ty&lt;/a&gt; too. So — we’re fairly well covered!&lt;/p&gt;
&lt;p&gt;But what if we need something vanilla, or something &lt;em&gt;more specific&lt;/em&gt;?&lt;/p&gt;
&lt;h2 id=&quot;pwa-from-scratch&quot;&gt;PWA from scratch &lt;a class=&quot;header-anchor&quot; href=&quot;#pwa-from-scratch&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. Make it installable&lt;/h3&gt;
&lt;p&gt;In practice, this is when the user can “add to home screen” on their mobile device. To achieve this, we need just a few things in &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;  &amp;lt;head&amp;gt;
    &amp;lt;title etc…&amp;gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+   &amp;lt;meta name=&amp;quot;theme-color&amp;quot; content=&amp;quot;#f00&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+   &amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; href=&amp;quot;/favicon.ico&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+   &amp;lt;link rel=&amp;quot;apple-touch-icon&amp;quot; href=&amp;quot;/path/to/icon.png&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+   &amp;lt;link rel=&amp;quot;manifest&amp;quot; href=&amp;quot;/path/to/your.webmanifest&amp;quot;&amp;gt;&lt;/span&gt;
  &amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we need to create those icons and the web manifest we refer to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;your.webmanifest&lt;/strong&gt; (you can call this anything, with a &lt;code&gt;.webmanifest&lt;/code&gt; extension)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Your site – a cool web app&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;short_name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Your site&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Here goes your awesome description&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;icons&amp;quot;&lt;/span&gt;: [
    {
      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;src&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;icon32.png&amp;quot;&lt;/span&gt;,
      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sizes&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;32x32&amp;quot;&lt;/span&gt;,
      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;image/png&amp;quot;&lt;/span&gt;
    },
    {
      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;src&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;icon512.png&amp;quot;&lt;/span&gt;,
      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sizes&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;512x512&amp;quot;&lt;/span&gt;,
      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;image/png&amp;quot;&lt;/span&gt;
    }
  ],
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;start_url&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;index.html&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;display&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;fullscreen&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;theme_color&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;#f00&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;background_color&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;#f00&amp;quot;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run a Lighthouse audit now, the PWA section will already show some passed checks, e.g. web app manifest meets requirements, sets a theme colour, provides a valid &lt;code&gt;apple-touch-icon&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It will, however, complain that your app “does not register a service worker.” Let’s get to that!&lt;/p&gt;
&lt;h3&gt;2. Register the service worker&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/h3&gt;
&lt;p&gt;This can be done &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Offline_Service_workers&quot;&gt;manually&lt;/a&gt;, but today we’ll use &lt;a href=&quot;https://developers.google.com/web/tools/workbox/modules/workbox-build&quot;&gt;workbox-build&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ yarn add --dev workbox-build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your build script, after building the app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { generateSW } = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;workbox-build&amp;#x27;&lt;/span&gt;)

generateSW({
  &lt;span class=&quot;hljs-attr&quot;&gt;globDirectory&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;dist&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-comment&quot;&gt;// or your build directory&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;swDest&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;dist/sw.js&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-comment&quot;&gt;// or wherever you want to put your service worker&lt;/span&gt;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last step is to register the service worker, and we’re good to go, with a green Lighthouse report.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;javascript&quot;&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;serviceWorker&amp;#x27;&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; navigator) {
    navigator.serviceWorker.register(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/path/to/service-worker.js&amp;#x27;&lt;/span&gt;);
  }
&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;A few notes&lt;/h3&gt;
&lt;p&gt;If you have a very large number of pages linked from your front page (e.g. a blog), it may be a good idea to limit the number of pre-fetched pages or use pagination to avoid downloading old entries unnecessarily.&lt;/p&gt;
&lt;p&gt;There are several service worker plugins for common build tools (e.g. Webpack, Rollup and Parcel) on &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; — usually, it’s a good idea to use one of those for seamless integration.&lt;/p&gt;
&lt;p&gt;Finally, if I’ve made a mistake, please &lt;a href=&quot;mailto:ekov@pm.me&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;See &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Introduction#Advantages_of_web_applications&quot;&gt;Advantages of web applications&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement&quot;&gt;Progressive Enhancement&lt;/a&gt;, both on MDN. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;By the way, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API&quot;&gt;service workers can do much more&lt;/a&gt; than make an app available offline. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Accessible lazy-loading with a &lt;noscript&gt; fallback</title>
    <link href="https://eszter.space/noscript-lazy-load/"/>
    <updated>2020-07-04T00:00:00+00:00</updated>
    <id>https://eszter.space/noscript-lazy-load/</id>
    <content type="html">&lt;p&gt;Designing for the &lt;a href=&quot;https://blockmetry.com/blog/javascript-disabled&quot;&gt;probably even less than 0.2%&lt;/a&gt; may sound like an overkill. Static sites (such as Gatsby, Svelte, Jekyll, Hugo, 11ty etc.) will cover non-script users most of the time. But if you need more fine-grained control, &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; is here to save the day.&lt;/p&gt;
&lt;p&gt;If you turn off JavaScript in a browser, and open up any React app, you’re quite likely to see the sentence &lt;em&gt;“This app works best with JavaScript enabled”&lt;/em&gt; or similar. Looking at the source code&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, this is the content of a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript&quot;&gt;&lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; tag&lt;/a&gt; — content that is only rendered when JavaScript is not available.&lt;/p&gt;
&lt;p&gt;You can put mostly anything inside &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt;, including &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags. It’s a good place for fallback content and stylesheets.&lt;/p&gt;
&lt;h2 id=&quot;a-real-life-use-case-lazy-loading-images&quot;&gt;A real-life use case: lazy-loading images &lt;a class=&quot;header-anchor&quot; href=&quot;#a-real-life-use-case-lazy-loading-images&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using a library like &lt;a href=&quot;https://github.com/verlok/vanilla-lazyload&quot;&gt;vanilla-lazyload&lt;/a&gt; already improves accessibility of a website, because it makes load time much faster, hence a smoother experience for users on average networks or devices.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The core concept behind lazy-loading is either omitting the &lt;code&gt;src&lt;/code&gt; and replacing it with a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes&quot;&gt;data attribute&lt;/a&gt;, or using a tiny version of the image with a blur-up effect.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; When the actual asset is loaded, it’s faded in using CSS. I’m omitting image width and height here, but you should try to set it to prevent content reflow.&lt;/p&gt;
&lt;h4&gt;HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;data-src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cat.png&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;alt&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;My cat&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;lazy&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cat&amp;quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;JS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; catImg = &lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;.getElementById(‘cat’)
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; fullsizeSrc = catImg.dataset.src &lt;span class=&quot;hljs-comment&quot;&gt;// from the data-src attribute&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; loader = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Image()
loader.onload = &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  catImg.src = fullsizeSrc
  catImg.classList.add(‘loaded’)
}
&lt;span class=&quot;hljs-comment&quot;&gt;// VERY important: .onload() has to come before .src assignment&lt;/span&gt;
loader.src = fullsizeSrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;CSS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.lazy&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class=&quot;hljs-attribute&quot;&gt;transition&lt;/span&gt;: opacity .&lt;span class=&quot;hljs-number&quot;&gt;5s&lt;/span&gt; ease;
}

&lt;span class=&quot;hljs-selector-class&quot;&gt;.lazy&lt;/span&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.loaded&lt;/span&gt; {
  &lt;span class=&quot;hljs-attribute&quot;&gt;opacity&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The (admittedly edge-case) problem with these is, &lt;strong&gt;if there’s no JavaScript, the images will never be loaded&lt;/strong&gt;. The solution is to add a non-lazy version for every lazy image, like so (and you can do this programmatically, at build time):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;  &amp;lt;img data-src=&amp;quot;cat.png&amp;quot; alt=&amp;quot;My cat&amp;quot; class=&amp;quot;lazy&amp;quot; id=&amp;quot;cat&amp;quot; /&amp;gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+ &amp;lt;noscript&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  &amp;lt;img src=&amp;quot;cat.png&amp;quot; alt=&amp;quot;My cat&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+ &amp;lt;/noscript&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will still be super fast, because whatever is inside &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; won’t load unless JavaScript is off. There’s still one issue to fix, though: hiding the lazy image placeholders when the &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; version kicks in. Here’s how, anywhere in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;hljs-addition&quot;&gt;+ &amp;lt;noscript&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+   &amp;lt;style type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+     .lazy { display: none; }&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+   &amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+ &amp;lt;/noscript&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can load an external stylesheet too.&lt;/p&gt;
&lt;p&gt;That’s it for inclusive lazy-loading — &lt;a href=&quot;https://github.com/c0derabbit/eszter.space/issues/new&quot;&gt;let me know&lt;/a&gt; if I’ve missed something!&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/c0derabbit/react-redux-ts/blob/master/public/index.html#L16&quot;&gt;react-redux-ts/index.html at master · c0derabbit/react-redux-ts · GitHub&lt;/a&gt; &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Let’s not forget that as developers and/or designers, both our devices and network are likely to be superior to that of the average user. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I’ve written about a &lt;a href=&quot;https://eszter.space/lazy-loading&quot;&gt;React solution for the blur-up lazy loading here&lt;/a&gt;. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Lily58, my second split ergo mech keyboard</title>
    <link href="https://eszter.space/lily58/"/>
    <updated>2020-10-10T00:00:00+00:00</updated>
    <id>https://eszter.space/lily58/</id>
    <content type="html">&lt;p&gt;This is just a short photo dump of my second keeb, a &lt;a href=&quot;https://splitkb.com/collections/keyboard-kits/products/lily58-kb-pcb-kit&quot;&gt;Lily58&lt;/a&gt;. If you are interested in what this is all about, please read &lt;a href=&quot;https://eszter.space/keeb&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/lily1.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;small&gt;All the components laid out&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/lily2.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;small&gt;Close-up on Pro Micros and their sockets&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/lily3.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;small&gt;The finished keyboard&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;P.s.: Yes, those are blank keycaps, and yes, I know where the keys are. Touch typing is super useful to learn, because it makes you write faster, reduces cognitive load, and it’s good for your posture. &lt;a href=&quot;https://www.typingclub.com/&quot;&gt;TypingClub&lt;/a&gt; is a great place to learn.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Naming things is hard</title>
    <link href="https://eszter.space/naming-things/"/>
    <updated>2020-11-23T00:00:00+00:00</updated>
    <id>https://eszter.space/naming-things/</id>
    <content type="html">&lt;p&gt;Or, what the heck does &lt;code&gt;getDisabledHours&lt;/code&gt; do?&lt;/p&gt;
&lt;p&gt;Every now and then, I come across a function call and have no clue what it does. I have to go to the definition and check the function body. One of my biggest fears in programming is writing code that’s difficult to understand — I don’t want my name to be the answer to &lt;em&gt;“Holy $*?!, who wrote this??”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve found two opportunities where naming can be improved greatly: pair coding and pull request reviews. Here follows an example, and the logic I’ve used to improve a function name for future programmers.&lt;/p&gt;
&lt;h2 id=&quot;how-to-recognise-a-bad-function-name&quot;&gt;How to recognise a bad function name &lt;a class=&quot;header-anchor&quot; href=&quot;#how-to-recognise-a-bad-function-name&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Usually, if you have to think (or worse, look) what a function does, there’s a better name for it. Take &lt;code&gt;getDisabledHours&lt;/code&gt; as an example, which is a method I’ve come across in a time picker. I don’t remember the surrounding code, but let’s assume it was something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;TimePicker disabledHours={getDisabledHours} /&amp;gt;
&lt;span class=&quot;hljs-comment&quot;&gt;/* TimePicker will likely expect an onChange handler too,
 * but we’ll ignore that for the sake of simplicity */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s the problem here?&lt;/p&gt;
&lt;p&gt;Ideally, the reader of this piece of code should be able to tell &lt;em&gt;why&lt;/em&gt; some hours are disabled, or &lt;em&gt;which&lt;/em&gt; hours are disabled, without looking at the function body. &lt;code&gt;getDisabledHours&lt;/code&gt; gives us no information on that. It only says that the disabled hours will be those that are disabled. That’s not too helpful.&lt;/p&gt;
&lt;h2 id=&quot;the-road-to-a-better-name&quot;&gt;The road to a better name &lt;a class=&quot;header-anchor&quot; href=&quot;#the-road-to-a-better-name&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Assuming the following function body:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;getDisabledHours&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; hours = []
  &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; hour = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;; hour &amp;lt; moment().hour(); hour++) {
    hours.push(hour)
  }
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; hours
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The question we need to ask is: &lt;strong&gt;What does this function do?&lt;/strong&gt; It creates an empty array of hours that it will disable, then adds all of the hours up to the current point in time — &lt;code&gt;moment().hour()&lt;/code&gt; — and returns them. In other words, &lt;strong&gt;disabled hours are those that are in the past&lt;/strong&gt;. (This could be a likely scenario in an appointment-booking system.)&lt;/p&gt;
&lt;p&gt;Turning this into code, &lt;code&gt;disabledHours&lt;/code&gt; will be the &lt;code&gt;hoursInThePast&lt;/code&gt;. Let’s try if that makes more sense:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;TimePicker disabledHours={hoursInThePast} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even our function makes more sense:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;hoursInThePast&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; hours = []
  &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; hour = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;; hour &amp;lt; moment().hour(); hour++) {
    hours.push(hour)
  }
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; hours
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reads much better — someone reading this piece of code will know that this time picker will allow the selection of hours in the future, but not in the past, without having to look at the function body.&lt;/p&gt;
&lt;p&gt;In short, if you have to think too much about what a function does without reading the function body, there’s probably a better name for it. A good way to find this is to answer the question &lt;em&gt;‘what does it do?’&lt;/em&gt; in human language, and then refine the answer until it can be used in the code.&lt;/p&gt;
&lt;p&gt;Naming things well can take some time, but it’s worth the effort. Future collaborators (including future you) will be grateful for a carefully chosen, descriptive name.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The importance of clean code</title>
    <link href="https://eszter.space/clean-code/"/>
    <updated>2020-12-26T00:00:00+00:00</updated>
    <id>https://eszter.space/clean-code/</id>
    <content type="html">&lt;p&gt;At the very beginning of my developer career, I had a conversation with a good friend and mentor, who (somewhat indirectly) recommended for me to read &lt;a href=&quot;https://www.goodreads.com/book/show/3735293-clean-code&quot;&gt;Clean Code&lt;/a&gt;. He told me about how, as a junior developer at a prominent startup&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, he was given the book, and was told to read it (before writing any code, if my memory is not mistaken).&lt;/p&gt;
&lt;p&gt;I thought, all right, it must be important then!&lt;/p&gt;
&lt;h2 id=&quot;a-good-start&quot;&gt;A good start &lt;a class=&quot;header-anchor&quot; href=&quot;#a-good-start&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Said startup was a really lucky pick for me too, for two reasons&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;they hired me, and&lt;/li&gt;
&lt;li&gt;they had an excellent library, which, of course, featured &lt;em&gt;Clean Code&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I got right to it.&lt;/p&gt;
&lt;p&gt;How slow I was progressing! I barely even understood JavaScript and Python, so the Java code samples in  &lt;em&gt;Clean Code&lt;/em&gt; seemed &lt;a href=&quot;https://futurama.fandom.com/wiki/Alienese&quot;&gt;Alienese&lt;/a&gt; to me. It must have taken me more than a year to finish, but I felt that it had a huge positive effect on my code.&lt;/p&gt;
&lt;p&gt;So did working with senior colleagues who believed in, and enforced, similar principles in the form of pair programming and code reviews.&lt;/p&gt;
&lt;h2 id=&quot;whats-clean-code&quot;&gt;What’s &lt;em&gt;clean code&lt;/em&gt;? &lt;a class=&quot;header-anchor&quot; href=&quot;#whats-clean-code&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To me, clean code is &lt;strong&gt;easy to read, navigate, and extend&lt;/strong&gt;. I don’t only understand it when I write it, but &lt;a href=&quot;https://eszter.space/naming-things/&quot;&gt;naming&lt;/a&gt; is clear enough for anyone in the future to know what a function does (or what a variable represents), without having to dig up the function body (or variable assignment). It is also well-organised, and doesn’t have surprising side effects.&lt;/p&gt;
&lt;p&gt;The book touches on naming, writing functions that do one thing, organising code, using (and not over-using) comments, unit testing, error handling etc.&lt;/p&gt;
&lt;p&gt;Writing clean code takes more time than &lt;em&gt;just working&lt;/em&gt; code, but working with clean code is significantly easier. Given how many times one iterates over the typical application these days, the benefits outweigh the cost by a large margin.&lt;/p&gt;
&lt;h2 id=&quot;a-message-from-the-future&quot;&gt;A message from the future &lt;a class=&quot;header-anchor&quot; href=&quot;#a-message-from-the-future&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some weeks ago, I got an email from a former colleague. He inherited a code base in large part written by myself&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;. In relation to that project, he shared this quote with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I could list all of the qualities that I notice in clean code, but there is one overarching quality that leads to all of them. &lt;strong&gt;Clean code always looks like it was written by someone who cares.&lt;/strong&gt; There is nothing obvious that you can do to make it better. All of those things were thought about by the code’s author, and if you try to imagine improvements, you’re led back to where you are, sitting in appreciation of the code someone left for you—&lt;strong&gt;code left by someone who cares deeply about the craft.&lt;/strong&gt;” &lt;cite&gt;— Michael Feathers&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, to be clear, I do &lt;em&gt;not&lt;/em&gt; think that my code is amazing — if anything, I know of that particular project that it was in constant need of cleaning up and improving. But, I &lt;em&gt;did&lt;/em&gt; care, and tried my best to keep it readable.&lt;/p&gt;
&lt;p&gt;Applying clean code principles won’t make your code perfect, and it won’t save you from bugs or refactoring. But it &lt;em&gt;will&lt;/em&gt; make someone’s life easier. And that’s worth all the effort.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Not incidentally, the same where I was applying for a position :) &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Two is an underestimate. I also had extremely helpful and knowledgeable colleagues, an inclusive culture open to juniors, a general ‘no stupid questions’ attitude etc… but these are less relevant for the purposes of this writing. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Not a huge feat when you work some years in a team of just a handful of developers. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Save users from selecting traffic lights with reCAPTCHA v3</title>
    <link href="https://eszter.space/recaptcha-v3/"/>
    <updated>2021-01-30T00:00:00+00:00</updated>
    <id>https://eszter.space/recaptcha-v3/</id>
    <content type="html">&lt;p&gt;Have you ever had to select all zebra crossings, buses, or traffic lights, sometimes repeatedly? It’s such a dreadful user experience, isn’t it?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Don’t do unto others what you don’t want done unto you.”
&lt;cite&gt; — Confucius&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In real Confucian spirit, just as we shouldn’t litter a website with popups, we shouldn’t ask anyone to perform 5-6 or more extra clicks just so they can log in, view content or search for something. But the marketing department / your boss / client / … want no spam.&lt;/p&gt;
&lt;h2 id=&quot;theres-a-better-way&quot;&gt;There’s a better way &lt;a class=&quot;header-anchor&quot; href=&quot;#theres-a-better-way&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;And it‘s Google’s &lt;a href=&quot;https://developers.google.com/recaptcha/docs/v3&quot;&gt;reCAPTCHA v3&lt;/a&gt;. It &lt;em&gt;”returns a score for each request without user friction. The score is based on interactions with your site and enables you to take an appropriate action for your site.”&lt;/em&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;What this means, essentially, is that you put the script on the site, and reCAPTCHA checks how likely the visitor is to be a real person with good intentions, and not a spam bot.&lt;/p&gt;
&lt;p&gt;It does have a server-side part, so you’ll need a small serverless lambda to make this work.&lt;/p&gt;
&lt;p&gt;Without further ado:&lt;/p&gt;
&lt;h2 id=&quot;heres-how-to-implement-it&quot;&gt;Here’s how to implement it &lt;a class=&quot;header-anchor&quot; href=&quot;#heres-how-to-implement-it&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This example assumes a form submission to a backend using &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;axios&lt;/code&gt; or similar. It is written in vanilla JS, but can be easily adapted to the frontend framework of your choice.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get an API key &lt;a href=&quot;https://g.co/recaptcha/v3&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Load the script in your HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;https://www.google.com/recaptcha/api.js?render=RECAPTCHA_SITE_KEY&amp;quot;&lt;/span&gt;
&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Execute reCAPTCHA before form submission on the &lt;strong&gt;client side&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;submitForm&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;event&lt;/span&gt;) &lt;/span&gt;{
  event.preventDefault()

  grecaptcha.ready(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) &lt;/span&gt;{
    grecaptcha.execute(RECAPTCHA_SITE_KEY, { &lt;span class=&quot;hljs-attr&quot;&gt;action&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;submit&amp;#x27;&lt;/span&gt; })
      .then(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;token&lt;/span&gt;) &lt;/span&gt;{
        &lt;span class=&quot;hljs-comment&quot;&gt;// submit form to server, including token&lt;/span&gt;
      })
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify reCAPTCHA token on the &lt;strong&gt;server side&lt;/strong&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// somewhere in your handler&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; verifyApi = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;https://www.google.com/recaptcha/api/siteverify&amp;#x27;&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; secret = process.env.RECAPTCHA_SECRET

  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; captchaRes = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; axios.post(
    &lt;span class=&quot;hljs-string&quot;&gt;`&lt;span class=&quot;hljs-subst&quot;&gt;${verifyApi}&lt;/span&gt;?secret=&lt;span class=&quot;hljs-subst&quot;&gt;${secret}&lt;/span&gt;&amp;amp;response=&lt;span class=&quot;hljs-subst&quot;&gt;${token}&lt;/span&gt;`&lt;/span&gt;
  )

  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!captchaRes.data.success) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Failed captcha verification&amp;#x27;&lt;/span&gt;
  }

  &lt;span class=&quot;hljs-comment&quot;&gt;// otherwise continue – your code here…&lt;/span&gt;
} &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt;(error) {
  &lt;span class=&quot;hljs-comment&quot;&gt;// handle error&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s about it — &lt;a href=&quot;https://developers.google.com/recaptcha/docs/v3&quot;&gt;check out the docs&lt;/a&gt; for specifics or different setups.&lt;/p&gt;
&lt;p&gt;P.s.: if you &lt;em&gt;really&lt;/em&gt; want the whole thing to be seamless, you can hide any traces of the reCAPTCHA by adding &lt;code&gt;display: none;&lt;/code&gt; to the class &lt;code&gt;.grecaptcha-badge&lt;/code&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; In any case, it’s not bad practice to show a bit of text saying (and linking to) ‘Protected by reCAPTCHA.’&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://developers.google.com/recaptcha/docs/v3&quot;&gt;reCAPTCHA docs&lt;/a&gt; &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The server-side code is intentionally more modern — adapt client-side as you see fit. The client-side code is written so it works as-is in most browsers.&lt;br&gt;&lt;br&gt;I also use axios here, but that’s unimportant. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I haven’t checked whether Google are OK with this, so if you use this in production, it’s best to check. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>A more ergonomic workspace, step by step</title>
    <link href="https://eszter.space/ergo/"/>
    <updated>2022-05-26T00:00:00+00:00</updated>
    <id>https://eszter.space/ergo/</id>
    <content type="html">&lt;p&gt;We spend a &lt;em&gt;lot&lt;/em&gt; of time sitting at our desks. This is not too healthy: we don’t get enough fresh air, don’t move enough, and, quite often, don’t pay enough attention to our joints and muscles.&lt;/p&gt;
&lt;p&gt;Take an &lt;a href=&quot;https://ehs.tamu.edu/media/1600945/officeselfassessment.pdf&quot;&gt;ergonomic self-assessment&lt;/a&gt;. Can you answer most questions with ‘Yes’?&lt;/p&gt;
&lt;p&gt;If so, go do something else, get some fresh air, thanks for reading my blog!&lt;/p&gt;
&lt;p&gt;If not, here are a few tips to improve your workspace, from practically free, to invest-in-your-health level. I’m not an ergonomics expert, just trying to maintain an ok-ish workspace – so please let me know if anything needs correction.&lt;/p&gt;
&lt;h2 id=&quot;the-basics&quot;&gt;The basics &lt;a class=&quot;header-anchor&quot; href=&quot;#the-basics&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. Sit (or stand) at a desk&lt;/h3&gt;
&lt;p&gt;I’m guilty – lying on the couch, melting into a deep sofa&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, or on your belly on the carpet. These are all very bad for posture, and might lead to problems with your joints.&lt;/p&gt;
&lt;p&gt;If something hurts, the first step is to sit or stand at a desk (or a table, at least.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;$: kitchen table&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$: common desk from IKEA or similar&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$$: sit/stand desk&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2. Look straight ahead&lt;/h3&gt;
&lt;p&gt;Screen height should be at eye level&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;, so it’s less strain on your eyes, and you can keep a straight neck and back. Make sure it’s not too high though, or your eyes will dry out.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;$: large books or thin boxes you already have at home - just put your laptop on top&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$: laptop stand&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$$: monitor(s)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Elevating a laptop to eye level will require an external keyboard and mouse.&lt;/strong&gt;&lt;br&gt;
What you use should depend on whether you have (or are actively seeking to avoid) problems with your wrists, or nah.&lt;/p&gt;
&lt;h4&gt;Option A: ‘my wrists are fine’&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;$: whatever keyboard or mouse you already have at home, or can find for cheap&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$: mechanical keyboard, proper mouse (apparently this is a thing - reddit)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$$: custom mechanical keyboard, for the fun!&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;Option B: my wrists are not fine :(&lt;/h4&gt;
&lt;p&gt;See at &lt;a href=&quot;#wrists&quot;&gt;Wrists&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;level-up-do-you-have-a-specific-issue&quot;&gt;Level up: do you have a specific issue? &lt;a class=&quot;header-anchor&quot; href=&quot;#level-up-do-you-have-a-specific-issue&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For me, it’s the wrists. I can sit in any weird position or the most uncomfortable chair all day and my back will be fine, but one day without a proper keyboard and trackball, and my wrists start aching.&lt;/p&gt;
&lt;p&gt;For others, they can pound away on Macbook Pros all day, but give them the wrong chair and they’ll have back pain within a few hours.&lt;/p&gt;
&lt;h3&gt;Wrists&lt;/h3&gt;
&lt;p&gt;First of all, make sure you’re maintaining correct posture. It might not seem related, but posture makes all the difference!&lt;/p&gt;
&lt;p&gt;You can also consider getting a (split ergo) mechanical keyboard. The split/ergo part is somewhat optional, but definitely get a mechanical one. Also, get a trackball&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;$: no truly budget option I’m afraid – try to get your employer to buy you one&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$: some simple keeb&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; with proper switches&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$$: &lt;a href=&quot;https://www.reddit.com/r/ErgoMechKeyboards&quot;&gt;custom split ergo boards&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Lower back&lt;/h3&gt;
&lt;p&gt;Invest in a chair that’s adjustable and has proper lumbar support. The various budget options will go from least to most effective, so if you already have problems, you really should put some money here.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;$: second-hand gaming chair. I once found one for £7 at a charity shop&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$: basic office chair from Office Depot, IKEA &amp;amp; co.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$$: full-support, everything adjustable, 5-star reviews&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Overweight, or hip problems&lt;/h3&gt;
&lt;p&gt;Could be too much sitting. Get a standing desk.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;$: big, big box on top of your existing desk, your stuff on top of that&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$: a small standing desk extension that goes on top of your existing desk&lt;/em&gt;&lt;br&gt;
&lt;em&gt;$$$: adjustable-height sit/stand desk&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;skills-and-lifestyle&quot;&gt;Skills and lifestyle &lt;a class=&quot;header-anchor&quot; href=&quot;#skills-and-lifestyle&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are some things you can do that are not directly related to your workspace setup, but will greatly affect the health of a life spent at a desk.&lt;/p&gt;
&lt;h3&gt;1. Move&lt;/h3&gt;
&lt;p&gt;Do some exercise, mostly stuff that’s easy on your joints. Swimming and long walks are both great. It doesn’t have to be a lot, just make sure it’s regular.&lt;/p&gt;
&lt;p&gt;Also make sure that you take regular breaks and step away from your desk every once in a while. Getting a glass of water is the perfect excuse.&lt;/p&gt;
&lt;h3&gt;2. Learn touch typing&lt;/h3&gt;
&lt;p&gt;Great, you’re working with a straight back and looking ahead at eye level. Now it’s time to keep your eyes on the screen (but don’t forget to look away occasionally!)&lt;/p&gt;
&lt;p&gt;Touch typing is super useful to learn, because it makes you write faster, reduces cognitive load, and it’s good for your posture. &lt;a href=&quot;https://www.typingclub.com/&quot;&gt;TypingClub&lt;/a&gt; is a great place to learn.&lt;/p&gt;
&lt;p&gt;🖥️&lt;/p&gt;
&lt;p&gt;I’m no expert to ergonomics, just an enthusiast, so please let me know&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt; if you have any corrections or suggestions. Keep healthy, drink water, &lt;a href=&quot;https://samu.space/etc/vegan/&quot;&gt;eat plants&lt;/a&gt;.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;An excellent way to awaken your &lt;a href=&quot;https://en.wikipedia.org/wiki/Repetitive_strain_injury&quot;&gt;RSI&lt;/a&gt;. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;To be more exact, the top 1/3 of your screen should be exactly at the same level as your eyes. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Logitech makes some inexpensive trackballs, I’m using a M570 myself. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Could be an entry-level mechanical gaming keyboard with Cherry MX switches (e.g. Corsair), or some other brands to check out: Anne Pro, Filco, Leopold, Varmilo, Vortex etc. &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;and held on to it like my life depended on it, until my bf arrived with the cash I didn’t have on me! &lt;a href=&quot;#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;ekov @ &lt;a href=&quot;http://pm.me/&quot;&gt;pm.me&lt;/a&gt;, without the spaces. &lt;a href=&quot;#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>On doing what you want (and getting there)</title>
    <link href="https://eszter.space/doing-what-you-want/"/>
    <updated>2022-11-14T00:00:00+00:00</updated>
    <id>https://eszter.space/doing-what-you-want/</id>
    <content type="html">&lt;p&gt;I first started programming a bit more than six years ago. I have worked in good teams, bad teams, for good money, for very little money… It’s been said enough times that between nice coworkers, cool product and money (maybe add exciting tech stack), you can choose two (or three if you are lucky), but not all of them.&lt;/p&gt;
&lt;p&gt;However, you don’t have to settle on the same aspects forever.&lt;/p&gt;
&lt;h2 id=&quot;entry-level-learn-as-much-as-you-can&quot;&gt;Entry level: learn as much as you can &lt;a class=&quot;header-anchor&quot; href=&quot;#entry-level-learn-as-much-as-you-can&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you start working as a junior, you might not be able to choose — you get what you can. So your goal, initially, should be to get to a point where you &lt;em&gt;can&lt;/em&gt; choose, as soon as possible. But how? Well, you have to get better at your craft, fast. You can do this in two ways (and please, do both):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Learning on the job. When you’re interviewing, look out for signs of support and opportunities for growth. For me, these include smart and experienced colleagues, using the latest technologies (at least partially), and assigned mentors for junior team members.&lt;/li&gt;
&lt;li&gt;Learning off the job. Read books (yes, physical, printed books), watch videos, solve coding challenges, work on pet projects. Play around with programming languages and frameworks that you don’t use at work.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;level-up-find-what-you-like-and-what-youre-good-at&quot;&gt;Level up: find what you like and what you’re good at &lt;a class=&quot;header-anchor&quot; href=&quot;#level-up-find-what-you-like-and-what-youre-good-at&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After the first 2-3 years, you might start noticing the areas where you generally make a strong contribution. Equally, you will know what you hate.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; These preferences might change a lot; for now, just start noticing them.&lt;/p&gt;
&lt;p&gt;This is also an excellent time to develop your T-shape&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; and mix things up a bit professionally. Transfer to a different team, a new project, or a new company. Changing employers is also more likely to bump your salary up — although at this stage, exposure to a wide array of tech and tools is a bigger investment.&lt;/p&gt;
&lt;h2 id=&quot;cash-rules-everything-around-me&quot;&gt;Cash rules everything around me &lt;a class=&quot;header-anchor&quot; href=&quot;#cash-rules-everything-around-me&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Got your exposure? Time for that famed ‘competitive compensation package’. When you’ve been in the trade for 3-4 years at least, you should be making more money than ever before,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; ask for a raise, get promoted, or find a better-paying job.&lt;/p&gt;
&lt;p&gt;Repeat this a few times, and come up with a realistic (read: fair and achievable) number that you desire to reach.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; This number will help you not to be blinded by the money later, when you have maximised your money-related happiness&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;, and other aspects of the job will become more important.&lt;/p&gt;
&lt;h2 id=&quot;getting-serious-specialize&quot;&gt;Getting serious: specialize &lt;a class=&quot;header-anchor&quot; href=&quot;#getting-serious-specialize&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you make enough money, aim for roles that require deeper knowledge in your preferred specialisation. This can be anything — I like making websites, so for me it’s usually a senior role within a growth team, with heavy use of SSR/SSG frameworks (such as Next.js and Gatsby), and lots of collaboration with design and marketing. You may like payments more, or building dashboards, who knows… whatever your chosen area is, make sure your next role allows you to dig deeper. Be the person people turn to when they have questions related to your field.&lt;/p&gt;
&lt;h2 id=&quot;doing-what-you-want&quot;&gt;Doing what you want* &lt;a class=&quot;header-anchor&quot; href=&quot;#doing-what-you-want&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After half a decade in the field, you can start thinking about what you’re &lt;em&gt;really&lt;/em&gt; interested in. Is there a company you’ve always wanted to work for? Maybe you want to start consulting? Start your own company? Teach instead of code? Now is the time to start exploring these ideas.&lt;/p&gt;
&lt;p&gt;This shouldn’t be about money now. Yes, easy to say with a software engineer salary — what I mean is, it’s not about the money in the sense that we should always choose the higher-paying offer. There is more to work than take-home pay.&lt;/p&gt;
&lt;p&gt;If you’ve followed some kind of a career strategy, with some luck, you can switch to something that is closer to your heart. You might have to take a pay cut — however, if you maxed out (or even surpassed) your ‘happiness number’ earlier, this should not be a problem.&lt;/p&gt;
&lt;p&gt;*100% satisfaction is, of course, not guaranteed. Even a company you admire can be an unpleasant workplace, have a toxic culture, pay too little, or disappoint you in other ways. But there’s always another opportunity just around the corner. Or the next. Keep looking, and good luck.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;But please don’t hate unit testing or typed languages just yet. They take some time to get used to, but trust me, both are invaluable tools that help us write better and more reliable code. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The stem of the T is your in-depth knowledge; the horizontal bar represents shallower but usable knowledge about neighbouring fields. If you are a frontend engineer, this means that you should be very good at one major JS framework (such as React), TypeScript etc., and should be able to communicate with backend engineers, understand some backend code; same with designers; maybe read up a bit about SEO, learn another frontend framework etc. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Unless you career-changed from investment banking or similar. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This should be an amount that allows you to save a significant percentage of your salary, while keeping up a comfortable lifestyle. Beware of &lt;a href=&quot;https://www.investopedia.com/terms/l/lifestyle-creep.asp&quot;&gt;lifestyle creep&lt;/a&gt; though! &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I swear I have read a study about this, but can’t find a source for the life of me. The key takeaway is that more money only makes us happier until a threshold is reached. This is different for everyone, but there is an average number out there somewhere. Ages ago in some study it was $70k. This might be outdated, but your number might not be as high as you expect it to be. &lt;a href=&quot;#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>Building a single-speed bicycle</title>
    <link href="https://eszter.space/bicycle/"/>
    <updated>2022-12-13T00:00:00+00:00</updated>
    <id>https://eszter.space/bicycle/</id>
    <content type="html">&lt;p&gt;Deep down I’ve wanted to build my own bicycle for a long while. I’ve only done very minor maintenance so far, so it’s definitely a step out of my comfort zone. I can feel this when I ask about components, but as one repair shop attendant, when I admitted not knowing all the jargon yet, told me: “the key here is &lt;em&gt;yet.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_front.jpg&quot; alt=&quot;A yellow road bike-type single speed bicycle with drop bars and white bar tape&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-idea&quot;&gt;The idea &lt;a class=&quot;header-anchor&quot; href=&quot;#the-idea&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A light-weight single speed bicycle with the prettiest frame I can find, silver colour hardware&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, and drop bars.&lt;/p&gt;
&lt;h2 id=&quot;planning&quot;&gt;Planning &lt;a class=&quot;header-anchor&quot; href=&quot;#planning&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I first stepped into the shop where I ordered the frame, and started to list some components, I was complimented for my preparedness. By this time I had spent hours reading about what components (and what sizes) I would need.&lt;/p&gt;
&lt;p&gt;I used a simple spreadsheet for this, with columns &lt;em&gt;description&lt;/em&gt;, &lt;em&gt;price approx., weight, tech specs, notes.&lt;/em&gt; This was of great help — the world of bicycle components is a vast jungle. Some components are dependent on others, e.g. the freewheel and crankset have to yield a good combination for speed and acceleration; handlebar size has to be compatible with the stem and the brake levers&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;, and so forth.&lt;/p&gt;
&lt;h2 id=&quot;sourcing-parts&quot;&gt;Sourcing parts &lt;a class=&quot;header-anchor&quot; href=&quot;#sourcing-parts&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is most definitely the longest part of building a bicycle. Even if you get most parts from a single place, they are not likely to have &lt;em&gt;everything&lt;/em&gt;, and there’s a lot of tiny compatibility details to pay attention to.&lt;/p&gt;
&lt;p&gt;In my case, however, the biggest challenge was finding silver hardware parts. For some reason, everything is black these days. It was near impossible to find a silver headset (and seatpost clamp!) My rear hub is also black, but who’s looking?&lt;/p&gt;
&lt;p&gt;I started by adding a status column to my spreadsheet, with which I would commute between two or three cycle shops (and the occasional webshop). With this many parts, it’s imperative to keep track of &lt;em&gt;to do/ordered/arrived&lt;/em&gt; items.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;
  Here’s a complete list of everything in my single-speed bicycle.
&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;frame set (including fork)&lt;/li&gt;
&lt;li&gt;headset&lt;/li&gt;
&lt;li&gt;bottom bracket&lt;/li&gt;
&lt;li&gt;freewheel&lt;/li&gt;
&lt;li&gt;crankset&lt;/li&gt;
&lt;li&gt;chain&lt;/li&gt;
&lt;li&gt;wheels (incl. rim tape)&lt;/li&gt;
&lt;li&gt;tyres&lt;/li&gt;
&lt;li&gt;inner tube&lt;/li&gt;
&lt;li&gt;stem&lt;/li&gt;
&lt;li&gt;spacers (optional)&lt;/li&gt;
&lt;li&gt;handlebars&lt;/li&gt;
&lt;li&gt;bar tape&lt;/li&gt;
&lt;li&gt;brake levers&lt;/li&gt;
&lt;li&gt;rim brake calipers&lt;/li&gt;
&lt;li&gt;brake cables&lt;/li&gt;
&lt;li&gt;cable housing; cable ends&lt;/li&gt;
&lt;li&gt;cable guides&lt;/li&gt;
&lt;li&gt;saddle&lt;/li&gt;
&lt;li&gt;seat post&lt;/li&gt;
&lt;li&gt;seat post clamp&lt;/li&gt;
&lt;li&gt;pedals&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2 id=&quot;putting-it-together&quot;&gt;Putting it together &lt;a class=&quot;header-anchor&quot; href=&quot;#putting-it-together&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most difficult parts of assembling a single-speed bicycle are building the wheels, and mounting the headset. I had the wheels built in a local bicycle shop (they were kind enough to even mount the freewheel).&lt;/p&gt;
&lt;p&gt;Mounting the headset would not be too complicated in itself, but it requires a special tool that is more on the expensive side. My workshop&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; also lacks a proper saw to cut off the top of the fork to the required length. The shop where I got the frame and fork kindly offered to do these for me.&lt;/p&gt;
&lt;p&gt;With these challenges out of the way, I was almost set to start — still missing a bottom bracket tool, which I quickly got from Decathlon.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;
Here’s a list of all the tools and bits I used to complete this build.
&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;allen keys (4, 5, 6mm)&lt;/li&gt;
&lt;li&gt;cone wrenches (adjustable / various sizes)&lt;/li&gt;
&lt;li&gt;bottom bracket tool&lt;/li&gt;
&lt;li&gt;chain tool&lt;/li&gt;
&lt;li&gt;wire cutter (the stronger the better)&lt;/li&gt;
&lt;li&gt;tyre levers&lt;/li&gt;
&lt;li&gt;pump&lt;/li&gt;
&lt;li&gt;lithium grease&lt;/li&gt;
&lt;li&gt;a piece of non-pile cloth to clean tools / parts / hands&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3&gt;Build log step by step&lt;/h3&gt;
&lt;p&gt;I started with putting the &lt;strong&gt;tyres and tubes&lt;/strong&gt; on the wheels. Add some air, too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_wheels.jpg&quot; alt=&quot;Two wheels with Panaracer Gravelking tyres&quot;&gt;&lt;br&gt;
&lt;small&gt;Wheels are ready to go.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;With the fork, stem and freewheel already attached to the frame, the next step was to &lt;strong&gt;install the bottom bracket&lt;/strong&gt;. The bottom bracket goes into the tubular hole at the middle/bottom of the frame, and allows the crankset&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; to rotate&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Two things are essential when installing the bottom bracket: knowing the &lt;strong&gt;thread direction&lt;/strong&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;, and &lt;strong&gt;greasing&lt;/strong&gt;. You’ll need a bottom bracket tool and a cone wrench.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_bottombracket.jpg&quot; alt=&quot;Bottom bracket inserted into a light yellow bicycle frame&quot;&gt;&lt;br&gt;
&lt;small&gt;Frame with bottom bracket inserted&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Next, I attached the &lt;strong&gt;crankset to the bottom bracket&lt;/strong&gt; using an allen key. Screws greased of course.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_crankset.jpg&quot; alt=&quot;A hand tightening the left crank arm using a red T-shaped hex tool&quot;&gt;&lt;br&gt;
&lt;small&gt;Tightening the crankset&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Time for the &lt;strong&gt;wheels and chain&lt;/strong&gt; to go into their place. My chain came sealed pre-greased, so it was quite messy to work with. Also, &lt;strong&gt;chain installation was the most fiddly thing in the whole build&lt;/strong&gt;. The chain has to be the right length, you sometimes need three hands to do it (three &lt;em&gt;small&lt;/em&gt; hands are ideal), plus there’s the problem of stiff chain links&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;&lt;/sup&gt; and chain too tight/too loose&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_chain.jpg&quot; alt=&quot;Detail of a bicycle chain and Sturmey Archer freewheel on a bicycle&quot;&gt;&lt;br&gt;
&lt;small&gt;No chain — no gain.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Handlebars!&lt;/strong&gt; Loosen the handlebar end of the stem, insert the handlebar, tighten.&lt;/p&gt;
&lt;p&gt;At this point, we can ride but we can’t stop. Time to fix that. Screws well-greased, I mounted the &lt;strong&gt;brake levers on the handlebars&lt;/strong&gt; and the &lt;strong&gt;brake calipers on the frame&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_brakes.jpg&quot; alt=&quot;A bicycle viewed from the front, with drop bars and brake levers, but without brake cables&quot;&gt;&lt;br&gt;
&lt;small&gt;Nothing can stop me now — this is a problem. This bicycle needs some cables.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Connecting the dots, next was &lt;strong&gt;brake wiring&lt;/strong&gt;. I cut the brake cable and housing to the correct length, added the tiny metal covers to each end (I suppose this is optional, for added protection — they don’t seem to do much). I first lead the cables through the brake levers, then through the housing, all the way to the calipers, then secured them and added a tiny metal cap for it to prevent from slipping (or scratching things?) I also fixed the back brake cable to the underside of the top tube, using two cable guides.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_cockpit.jpg&quot; alt=&quot;A bicycle cockpit from the front: drop bars with white perforated bar tape, brake levers and white cabling.&quot;&gt;&lt;br&gt;
&lt;small&gt;Cabled and bar-taped.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Finishing touches, in no particular order: &lt;strong&gt;adding the bar tape&lt;/strong&gt;, &lt;strong&gt;saddle and seat post&lt;/strong&gt; (the seat post well-greased, of course) secured with a seat post clamp, and &lt;strong&gt;pedals&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;some-final-thoughts&quot;&gt;Some final thoughts &lt;a class=&quot;header-anchor&quot; href=&quot;#some-final-thoughts&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The build complete, I adjusted the brakes and tyre pressure, and &lt;strong&gt;went for a test ride&lt;/strong&gt;. Success: nothing came loose; the bike feels very smooth and lightweight, and is capable of taking me from A to B.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/img/bb_full.jpg&quot; alt=&quot;A light yellow single speed, road-bike type bicycle with white drop bars and white saddle.&quot;&gt;&lt;br&gt;
&lt;small&gt;Ready to ride!&lt;/small&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The total cost of the build was roughly 630€, all new parts. This includes the purchase of some specific but inexpensive tools.&lt;/li&gt;
&lt;li&gt;If you decide to try this at home, do check size compatibility of &lt;em&gt;every single component&lt;/em&gt;. Your local bike shop will help.&lt;/li&gt;
&lt;li&gt;This was immensely much fun, and not too complicated. 10/10 would do again.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This turned out to be a major difficulty, but more on this later. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Which in turn have to be compatible with the type of brake cable. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Just some tools on a shelf really. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;or &lt;em&gt;chainset&lt;/em&gt;, depending on where you live &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;and by doing so, drive the chain, turn the wheels, advance the bicycle. &lt;a href=&quot;#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Usually, but not always, this is ‘backwards’/left on the right side, and normal direction on the left side. &lt;a href=&quot;#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;fixed by repeatedly moving the chain the way it’s supposed to, and forcing it the way it’s not supposed to — for example, by inserting something in between the &lt;em&gt;outer&lt;/em&gt; chain links and forcing them a tiny tiny bit apart. &lt;a href=&quot;#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Good news if your frame has a horizontal dropout: you can adjust the chain’s tightness by moving the rear wheel forward or back. &lt;a href=&quot;#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>My brain without a smart phone</title>
    <link href="https://eszter.space/low-tech/"/>
    <updated>2025-02-06T00:00:00+00:00</updated>
    <id>https://eszter.space/low-tech/</id>
    <content type="html">&lt;p&gt;Lately, I’ve spent roughly 3 to 5 hours a day looking at my phone. Classic doom scrolling: watching reels, sending reels, laughing at reels that my friends sent me. Reading the news, online window-shopping, curating my mood boards — while algorithms were curating my mood.&lt;/p&gt;
&lt;p&gt;This was a problem.&lt;/p&gt;
&lt;p&gt;I did not think the smart phone itself was such a big issue, but I did not function in a way I would have liked. I was tired all the time. Fake-painfully aware of the areas of my life that needed improvement, but lacking the time or energy to act on them.&lt;/p&gt;
&lt;p&gt;I knew I was spending too much time just sitting on the couch with my phone in my hand, staring at content spoon-fed to me, but I had no idea how powerful it would be to just stop doing that.&lt;/p&gt;
&lt;h3&gt;The problem with algorithms, or, things were better before&lt;/h3&gt;
&lt;p&gt;I strongly believe that personal technology&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; peaked in the early 2000s, when portable devices were small, did one thing, and did it well.&lt;/p&gt;
&lt;p&gt;Phones were there to have actual, real-time conversations. Texting had a limit due to cost and typing speed,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; but this limit encouraged creativity with written language.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Music players had an intention behind them. You could show music to your friends. You had an actual, curated-by-you, thought-over selection of &lt;em&gt;limited&lt;/em&gt; music on your tiny device. You could make the choice of quality versus quantity.&lt;/p&gt;
&lt;p&gt;This all needed consideration. Intention. Preferences.&lt;/p&gt;
&lt;p&gt;Streaming services and social media do the selection for you. They take away the freedom of exploring, the excitement of discovering something new on your own. All of this at our fingertips, 24/7, with practically unlimited data, feeding us the same echo-chamber-curated content anytime, anywhere.&lt;/p&gt;
&lt;p&gt;We rely on these algorithms for our daily dopamine rush. It’s a vicious cycle — we need a lot of it because we are deprived of it: we don’t eat well, we don’t sleep well, we don’t move enough. And as long as we are addicted to our screens, we won’t, either.&lt;/p&gt;
&lt;p&gt;Plus… where’s the intention? Where’s creativity? Where are interests that differ from one friend to another? Where are &lt;a href=&quot;https://www.cameronsworld.net/&quot;&gt;beautifully weird personal websites&lt;/a&gt;? &lt;a href=&quot;https://www.reddit.com/r/dumbphones/comments/1ii5rv5/is_it_just_me_or_is_technology_making_life_feel/&quot;&gt;Being human?&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;I tried to improve things.&lt;/h3&gt;
&lt;p&gt;I tried to limit screen time, but I bypassed my self-imposed limits &lt;em&gt;all the time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://www.wired.com/story/grayscale-ios-android-smartphone-addiction/&quot;&gt;set my phone to greyscale&lt;/a&gt;. Try it — when you switch back to colour, you’ll find the screen harsh, almost yelling at you, the colours jumping at you aggressively, trying (and, on the long run, succeeding) to keep you entertained.&lt;/p&gt;
&lt;p&gt;I deleted most of my social media accounts&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;, and asked my friends to call or text instead (they could still WhatsApp me — but I could not guarantee response times.)&lt;/p&gt;
&lt;p&gt;These measures helped somewhat, but the smartphone is a powerful mind-hijacking machine, engineered by great minds who know more about how our brains work than we do. I needed more drastic measures.&lt;/p&gt;
&lt;h3&gt;Enter the dumb phone&lt;/h3&gt;
&lt;p&gt;The first modern dumb phone that really fascinated me was the &lt;a href=&quot;https://www.thelightphone.com/original-light-phone&quot;&gt;original Light Phone&lt;/a&gt;, which &lt;em&gt;only made phone calls.&lt;/em&gt; That’s right: not even text.&lt;/p&gt;
&lt;p&gt;I do find texting very practical, so that was a must. I started researcing dumb phones.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I bought a &lt;a href=&quot;https://www.punkt.ch/en/products/mp02-4g-mobile-phone/&quot;&gt;Punkt MP02&lt;/a&gt; (this was a few years back on a previous let’s-go-low-tech rampage), on which you could probably only text if you got to your teens before smart phones became ubiquitous. It has actual buttons, no touch screen. It ended up in my drawer because, despite its nice shape and tactility, it was not pleasant to use. It runs a horribly done Android clone with the failed promise of a messaging app,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt; and some quite ugly ring tones.&lt;/p&gt;
&lt;p&gt;Then I found it: a tiny E Ink device, the Light Phone’s successor, &lt;strong&gt;&lt;a href=&quot;https://www.thelightphone.com/lightii&quot;&gt;Light Phone II&lt;/a&gt;.&lt;/strong&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You can only do a few things with it: make calls, send messages, get basic (text-based) directions, listen to music,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;&lt;/sup&gt; take notes, and look at your calendar. I love it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eszter.space/assets/lp.jpg&quot; alt=&quot;hand holding a Light Phone II&quot;&gt;
&lt;small&gt;The Light Phone II’s main screen&lt;/small&gt;&lt;/p&gt;
&lt;h3&gt;Going dumb-phone, no-social-media changed how my brain and body works.&lt;/h3&gt;
&lt;p&gt;I feel less tense and more chill in general. Because of less blue light exposure during the day and especially in the evening, my biological clock went back to a more natural rhythm in just a few days. I can fall asleep. I wake up between 6–7 am on my own, without an alarm, every day. Energized.&lt;/p&gt;
&lt;p&gt;I am productive in the morning. Not productive like society expects me to, rather in a way that makes sense for us as humans. Sometimes I play an instrument, I read or write for an hour or two, I listen to a record while ironing, or I re-pot some house plants.&lt;/p&gt;
&lt;p&gt;And I enjoy all of these things immensely. I don’t get bored that easily.&lt;/p&gt;
&lt;p&gt;I was over at a friend’s place the other night and she asked if I was tired — I didn’t &lt;em&gt;feel&lt;/em&gt; tired, but it was already dark outside, so I had started to slow down.&lt;/p&gt;
&lt;p&gt;So… just living a more natural rhythm. It’s like my brain is on a sabbatical.&lt;/p&gt;
&lt;h3&gt;What now?&lt;/h3&gt;
&lt;p&gt;I still have a long way to go: about an hour of screen time on my iPhone for various purposes (mostly WhatsApp and admin tasks.) Deleting LinkedIn. Deleting Instagram (not just the app, the whole account plus my dog’s account. I know, it’s bad.)&lt;/p&gt;
&lt;p&gt;So, for the foreseeable future, this website is my online presence.&lt;/p&gt;
&lt;p&gt;Now go, delete your socials, or at least read some &lt;a href=&quot;https://www.jaronlanier.com/&quot;&gt;Jaron Lanier&lt;/a&gt;.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;music players, mobile phones, cameras etc. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Not that this prevented us from texting &lt;em&gt;a lot&lt;/em&gt; in our teens. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Sometimes frowned upon, especially back in the day, but personally, I don’t see anything wrong with this. It was way milder and more connected to actual language than today’s gen-z and alpha-ese. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;including Facebook, Messenger and Pinterest, leaving Instagram for the time being &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I mostly perused Reddit, but the &lt;a href=&quot;https://josebriones.org/dumbphone-finder&quot;&gt;Dumbphone Finder&lt;/a&gt; is a pretty extensive list with filter options by low-tech advocate Jose Briones. &lt;a href=&quot;#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;A horribly executed Signal fork that is nearly impossible to set up, and only allows users to have Signal on the Punkt device. Pretty bad. &lt;a href=&quot;#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There’s a &lt;a href=&quot;https://www.thelightphone.com/lightiii&quot;&gt;Light Phone III&lt;/a&gt; too, but it’s got a few things I don’t like or need: its size (much bigger), an OLED screen (I’m a long time E Ink fangirl), and a camera. &lt;a href=&quot;#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;manually uploaded and limited to 1GB :) &lt;a href=&quot;#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry>
    <title>SMS-based ChatGPT for low-tech folks</title>
    <link href="https://eszter.space/sms-chatgpt/"/>
    <updated>2025-02-23T00:00:00+00:00</updated>
    <id>https://eszter.space/sms-chatgpt/</id>
    <content type="html">&lt;h2 id=&quot;motivation&quot;&gt;Motivation &lt;a class=&quot;header-anchor&quot; href=&quot;#motivation&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recently &lt;a href=&quot;https://eszter.space/low-tech&quot;&gt;I went low-tech&lt;/a&gt;, and I’ve been super happy with &lt;a href=&quot;https://www.thelightphone.com/lightii&quot;&gt;Light Phone II&lt;/a&gt;’s very limited capabilities&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; besides texting and calling.&lt;/p&gt;
&lt;p&gt;One thing was missing though: ChatGPT. I’ve gotten used to querying random stuff, and wanted a way to access it from my dumb phone.&lt;/p&gt;
&lt;p&gt;So I thought — wait a minute. We have all the technology available to build this.&lt;/p&gt;
&lt;h2 id=&quot;the-concept&quot;&gt;The concept &lt;a class=&quot;header-anchor&quot; href=&quot;#the-concept&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;text a number with a question&lt;/li&gt;
&lt;li&gt;run a serverless function that calls OpenAI API with some preset rules&lt;/li&gt;
&lt;li&gt;text back with an answer.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;making-it-happen&quot;&gt;Making it happen &lt;a class=&quot;header-anchor&quot; href=&quot;#making-it-happen&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Obviously, I had a short chat about it with ChatGPT, and then started coding with &lt;a href=&quot;https://www.cursor.com/&quot;&gt;Cursor&lt;/a&gt;. There are a few &lt;strong&gt;pre-requisites&lt;/strong&gt; for this, and it &lt;strong&gt;costs money&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We don’t need much to set this up:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write and deploy the serverless handler&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; (see code below)&lt;/li&gt;
&lt;li&gt;Get your OpenAI API key and upgrade to a paid plan&lt;/li&gt;
&lt;li&gt;Get a paid Twilio plan and buy a phone number&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Set up webhook URL in Twilio dashboard to point to your API endpoint.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Somewhat abbreviated:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; twilio = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;twilio&amp;#x27;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { OpenAI } = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;openai&amp;#x27;&lt;/span&gt;)

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; openai = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; OpenAI({ &lt;span class=&quot;hljs-attr&quot;&gt;apiKey&lt;/span&gt;: process.env.OPENAI_API_KEY })

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; twilioClient = twilio(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN
)

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; sendSMS = &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;to, body&lt;/span&gt;) =&amp;gt;&lt;/span&gt; twilioClient.messages.create({
  body,
  to,
  &lt;span class=&quot;hljs-attr&quot;&gt;from&lt;/span&gt;: process.env.TWILIO_PHONE_NUMBER
})

&lt;span class=&quot;hljs-built_in&quot;&gt;module&lt;/span&gt;.exports = &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { Body, From } = req.body

    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (From !== process.env.ALLOWED_PHONE_NUMBER) {
      &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; res.status(&lt;span class=&quot;hljs-number&quot;&gt;403&lt;/span&gt;).end()
    }

    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; completion = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; openai.chat.completions.create({
      &lt;span class=&quot;hljs-attr&quot;&gt;model&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;gpt-4o&amp;#x27;&lt;/span&gt;,
      &lt;span class=&quot;hljs-attr&quot;&gt;messages&lt;/span&gt;: [
        {
          &lt;span class=&quot;hljs-attr&quot;&gt;role&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;system&amp;#x27;&lt;/span&gt;,
          &lt;span class=&quot;hljs-attr&quot;&gt;content&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;please provide concise answers under 160 characters.&amp;#x27;&lt;/span&gt;
        },
        { &lt;span class=&quot;hljs-attr&quot;&gt;role&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;user&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;content&lt;/span&gt;: Body }
      ],
      &lt;span class=&quot;hljs-attr&quot;&gt;max_tokens&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;60&lt;/span&gt;
    })

    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; sendSMS(From, completion.choices[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;].message.content)

    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; res.status(&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;).end()
  } &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (error) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; res.status(&lt;span class=&quot;hljs-number&quot;&gt;500&lt;/span&gt;).end()
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I do some error handling as well, but that’s about it. &lt;a href=&quot;https://github.com/eszterkv/sms&quot;&gt;Here’s the full source code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;caveats&quot;&gt;Caveats &lt;a class=&quot;header-anchor&quot; href=&quot;#caveats&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I still don’t know how much this will cost, I’ve added some estimates to the repo readme, but Twilio pricing is highly dependent on location.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Also, not too conveniently, the reply comes from a different number than the one I have to text. This could be because my reserved number is not local. I will figure this out soon and update accordingly.&lt;/p&gt;
&lt;h2 id=&quot;is-it-worth-it&quot;&gt;Is it worth it? &lt;a class=&quot;header-anchor&quot; href=&quot;#is-it-worth-it&quot;&gt;•&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For me, absolutely! I have a way to ask ChatGPT questions, and I don’t even need an internet connection for that. This could have a lot of other potential use cases where internet connection is limited or not available.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Namely, a handy directory offering key info on venues’ addresses, contact and opening times; and a text-based route planner that supports various transportation methods. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;You know I’m a Vercel fangirl. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There are other options, too — I am yet to figure out which is the most economical on the long run. Local phone numbers are more convenient, but some countries are cheaper than others. This mostly applies if you are outside of the US. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;For example, buying a Hungarian phone number would cost me $35/mo, whereas a Swedish number only costs me $3. &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
</feed>
