<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="atom-to-html.xsl"?>

<feed xmlns="http://www.w3.org/2005/Atom">
<title>Published articles</title>
<generator uri="http://tt-rss.org/">Tiny Tiny RSS/20.01-5fc499e</generator>
<updated>2019-01-28T00:00:00+00:00</updated>
<id>https://tt-rss.goodevilgenius.org/public.php?op=rss&amp;id=-2&amp;key=c71f53046367b28e22188dd0c7e799f36a0afecc</id>
<link href="https://tt-rss.goodevilgenius.org/public.php?op=rss&amp;id=-2&amp;key=c71f53046367b28e22188dd0c7e799f36a0afecc" rel="self"/>

<link href="https://tt-rss.goodevilgenius.org" rel="alternate"/>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2026-05-31:/459312</id>
	<link href="https://languagehat.com/the-10-most-spoken-languages/" rel="alternate" type="text/html"/>
	<title type="html">The 10 Most Spoken Languages.</title>
	<summary type="html"><![CDATA[<p>Courtesy of Stu Clayton, a brief and enjoyable video clip in which two guys try to guess the ten mos...</p>]]></summary>
	<content type="html"><![CDATA[<p>Courtesy of Stu Clayton, a brief and enjoyable <a href="https://www.youtube.com/shorts/KeasuYzYIjk" rel="noopener noreferrer" target="_blank">video clip</a> in which two guys try to guess the ten most spoken languages in the world (lumping together first- and second-language speakers).  Stu says &ldquo;Being ignorant, I was surprised by nr 10&rdquo;; I wasn&rsquo;t surprised, but I did enjoy the ride, as I hope will you.</p>]]></content>
	<updated>2026-05-31T21:24:22+00:00</updated>
	<author><name>languagehat</name></author>
	<source>
		<id>http://languagehat.com</id>
		<link rel="self" href="http://languagehat.com"/>
		<updated>2026-05-31T21:24:22+00:00</updated>
		<title>languagehat.com</title></source>

	<category term="uncategorized"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2026-05-26:/459262</id>
	<link href="https://kottke.org/26/05/0049017-all-three-won-their-respe" rel="alternate" type="text/html"/>
	<title type="html">The first-ever Enhanced Games (open to those using...</title>
	<summary type="html"><![CDATA[<p>The first-ever Enhanced Games (open to those using performance-enhancing drugs) featured ...</p>]]></summary>
	<content type="html"><![CDATA[<p>The first-ever Enhanced Games (open to those using performance-enhancing drugs) featured 42 athletes, three of whom were competing clean. <a href="https://sports.yahoo.com/articles/clean-athletes-stole-show-enhanced-161747323.html" rel="noopener noreferrer" target="_blank">All three won their respective events</a>.
</p>]]></content>
	<updated>2026-05-26T20:50:00+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2026-05-26T20:50:00+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2026-01-04:/456944</id>
	<link href="https://antonz.org/ai-advocacy/" rel="alternate" type="text/html"/>
	<title type="html">Fear is not advocacy</title>
	<summary type="html"><![CDATA[<p>AI advocates seem to be the only kind of technology advocates who feel this imminent urge to constan...</p>]]></summary>
	<content type="html"><![CDATA[<p>AI advocates seem to be the only kind of technology advocates who feel this imminent urge to constantly criticize developers for not being excited enough about their tech.</p>
<p>It would be crazy if I presented new Go features like this:</p>
<blockquote>
<p>If you still don't use the <code>synctest</code> package, all your systems will eventually succumb to concurrency bugs.</p>
</blockquote>
<p>or</p>
<blockquote>
<p>If you don't use iterators, you have absolutely nothing interesting to build.</p>
</blockquote>
<p>The job of an advocate is to spark interest, not to reproach people or instill FOMO. And yet that's exactly what AI advocates do.</p>
<p>What a weird way to advocate.</p>
<h3>It's okay not to be early</h3>
<p>This whole "devote your life to AI right now, or you'll be out of a job soon" narrative is false.</p>
<p>You don't have to be a world-class algorithm expert to write good software. You don't have to be a Linux expert to use containers. And you don't have to spend all your time now trying to become an expert in chasing ever-changing AI tech.</p>
<p>As with any new technology, developers adopting AI typically fall into four groups: early adopters, early majority, late majority, and laggards. Right now, AI advocates are trying to shame everyone into becoming early adopters. But it's perfectly okay to wait if you're sceptical. Being part of the late majority is a safe and reasonable choice. If anything, you'll have fewer bugs to deal with.</p>
<p>As the industry adopts AI practices, you'll naturally absorb just the right amount of them.</p>
<p>You are going to be fine.</p>]]></content>
	<updated>2026-01-04T12:00:00+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://antonz.org/</id>
		<link rel="self" href="https://antonz.org/"/>
		<updated>2026-01-04T12:00:00+00:00</updated>
		<title>Anton Zhiyanov</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2024-06-24:/448602</id>
	<link href="https://goodevilgenius.org/2024/06/24/golang-Understanding-the-difference-between-nil-pointers-and-nil-interfaces/" rel="alternate" type="text/html"/>
	<title type="html">golang: Understanding the difference between nil pointers and nil interfaces</title>
	<summary type="html"><![CDATA[<p>I was thinking a bit about the different ways in which nil works in go, and how sometimes, something...</p>]]></summary>
	<content type="html"><![CDATA[<p>I was thinking a bit about the different ways in which nil works in go, and how sometimes, something can be both nil and not nil at the same time.</p><p><a href="https://go.dev/play/p/wgMSKWFjtvv" rel="noopener noreferrer" target="_blank">Here</a> is a little example of something that can be a nil pointer, but not a nil interface. Let&rsquo;s walk through what that means.</p><h2><a href="https://tt-rss.goodevilgenius.org/#interfaces" title="Interfaces" rel="noopener noreferrer" target="_blank"></a>Interfaces</h2><p>First, go has a concept of interfaces, which are similar, but not quite the same as interfaces in some object-oriented languages (go is not OOP by most definitions). In go, an interface is a type that defines functions that another type must implement to satisfy the interface. This allows us to have multiple concrete types that can satisfy an interface in different ways.</p><p>For example, <code>error</code> is a built-in interface that has a single method. It looks like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br></pre></td><td><pre><span><span>type</span> <span>error</span> <span>interface</span> {</span><br><span>    Error() <span>string</span></span><br><span>}</span><br></pre></td></tr></table></figure><p>Any type that wants to be used as an error must have a method called <code>Error</code> which returns a string. For example, the following code could be used:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></pre></td><td><pre><span><span>type</span> ErrorMessage <span>string</span></span><br><span></span><br><span><span><span>func</span> <span>(em ErrorMessage)</span></span> Error() <span>string</span> {</span><br><span>    <span>return</span> <span>string</span>(em)</span><br><span>}</span><br><span></span><br><span><span><span>func</span> <span>DoSomething</span><span>()</span></span> <span>error</span> {</span><br><span>    <span>// Try to do something, and it fails.</span></span><br><span>    <span>if</span> somethingFailed {</span><br><span>        <span>var</span> err ErrorMessage = <span>"This failed"</span></span><br><span>        <span>return</span> err</span><br><span>    }</span><br><span>    <span>return</span> <span>nil</span></span><br><span>}</span><br><span></span><br><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    err := DoSomething()</span><br><span>    <span>if</span> err != <span>nil</span> {</span><br><span>        <span>panic</span>(err)</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>Notice in this example that <code>DoSomething</code> returns an <code>error</code> if something goes wrong. We can use our <code>ErrorMessage</code> type, because it has the <code>Error</code> function, which returns a string, and therefore implements the <code>error</code> interface.If no error occurred, we returned nil.</p><h2><a href="https://tt-rss.goodevilgenius.org/#pointers" title="Pointers" rel="noopener noreferrer" target="_blank"></a>Pointers</h2><p>In go, pointers point to a value, but they can also point to no value, in which case the pointer is nil. For example:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></pre></td><td><pre><span><span>var</span> i *<span>int</span> = <span>nil</span></span><br><span></span><br><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    <span>if</span> i == <span>nil</span> {</span><br><span>        j := <span>5</span></span><br><span>        i = &amp;j</span><br><span>    }</span><br><span>    fmt.Println(<span>"i is"</span>, *i)</span><br><span>}</span><br></pre></td></tr></table></figure><p>In this case, the <code>i</code> variable is a pointer to an int. It starts out as a nil pointer, until we create an int, and point it to that.</p><h2><a href="https://tt-rss.goodevilgenius.org/#pointers-and-interfaces" title="Pointers and interfaces" rel="noopener noreferrer" target="_blank"></a>Pointers and interfaces</h2><p>Since user-defined types can have functions (methods) attached, we can also have functions for pointers to types. This is a very common practice in go. This also means that pointers can also implement interfaces. In this way, we could have a value that is a non-nil interface, but still a nil pointer. Consider the following code:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></pre></td><td><pre><span><span>type</span> TruthGetter <span>interface</span> {</span><br><span>    IsTrue() <span>bool</span></span><br><span>}</span><br><span></span><br><span><span><span>func</span> <span>PrintIfTrue</span><span>(tg TruthGetter)</span></span> {</span><br><span>    <span>if</span> tg == <span>nil</span> {</span><br><span>        fmt.Println(<span>"I can't tell if it's true"</span>)</span><br><span>        <span>return</span></span><br><span>    }</span><br><span>    <span>if</span> tg.IsTrue() {</span><br><span>        fmt.Println(<span>"It's true"</span>)</span><br><span>    } <span>else</span> {</span><br><span>        fmt.Println(<span>"It's not true"</span>)</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>Any type that has an <code>IsTrue() bool</code> method can be passed to <code>PrintIfTrue</code>, but so can <code>nil</code>. So, we can do <code>PrintIfTrue(nil)</code> and it will print &ldquo;I can&rsquo;t tell if it&rsquo;s true&rdquo;.</p><p>We can also do something simple like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></pre></td><td><pre><span><span>type</span> Truthy <span>bool</span></span><br><span></span><br><span><span><span>func</span> <span>(ty Truthy)</span></span> IsTrue() <span>bool</span> {</span><br><span>    <span>return</span> <span>bool</span>(ty)</span><br><span>}</span><br><span></span><br><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    <span>var</span> ty Truthy = <span>true</span></span><br><span>    PrintIfTrue(ty)</span><br><span>}</span><br></pre></td></tr></table></figure><p>This will print &ldquo;It&rsquo;s true&rdquo;.</p><p>Or, we can do something more complicated, like:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></pre></td><td><pre><span><span>type</span> TruthyNumber <span>int</span></span><br><span></span><br><span><span><span>func</span> <span>(tn TruthyNumber)</span></span> IsTrue() <span>bool</span> {</span><br><span>    <span>return</span> tn &gt; <span>0</span></span><br><span>}</span><br><span></span><br><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    <span>var</span> tn TruthyNumber = <span>-4</span></span><br><span>    PrintIfTrue(tn)</span><br><span>}</span><br></pre></td></tr></table></figure><p>That will print &ldquo;It&rsquo;s not true&rdquo;. Neither of these examples are pointers, and so there&rsquo;s no chance for a nil with either of these types, but consider this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></pre></td><td><pre><span><span>type</span> TruthyPerson <span>struct</span> {</span><br><span>    FirstName <span>string</span></span><br><span>    LastName <span>string</span></span><br><span>}</span><br><span></span><br><span><span><span>func</span> <span>(tp *TruthyPerson)</span></span> IsTrue() <span>bool</span> {</span><br><span>    <span>return</span> tp.FirstName != <span>""</span> &amp;&amp; tp.LastName != <span>""</span></span><br><span>}</span><br></pre></td></tr></table></figure><p>In this case <code>TruthyPerson</code> does not implement <code>TruthGetter</code>, but <code>*TruthyPerson</code> does. So, this should work:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></pre></td><td><pre><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    tp := &amp;TruthyPerson{<span>"Jon"</span>, <span>"Grady"</span>}</span><br><span>    PrintIfTrue(tp)</span><br><span>}</span><br></pre></td></tr></table></figure><p>This works because <code>tp</code> is a pointer to a <code>TruthyPerson</code>. However, if the pointer is nil, we&rsquo;ll get a panic.</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></pre></td><td><pre><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    <span>var</span> tp *TruthyPerson</span><br><span>    PrintIfTrue(tp)</span><br><span>}</span><br></pre></td></tr></table></figure><p>This will panic. However, the panic doesn&rsquo;t happen in <code>PrintIfTrue</code>. You would think it&rsquo;s fine, because <code>PrintIfTrue</code> checks for nil. But, here&rsquo;s the issue. It&rsquo;s checking nil against a <code>TruthGetter</code>. In other words, it&rsquo;s checking for a nil interface, but not a nil pointer. And in <code>func (tp *TruthyPerson) IsTrue() bool</code>, we don&rsquo;t check for a nil. In go, we can still call methods on a nil pointer, so the panic happens there. The fix is actually pretty easy.</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></pre></td><td><pre><span><span><span>func</span> <span>(tp *TruthyPerson)</span></span> IsTrue() <span>bool</span> {</span><br><span>    <span>if</span> tp == <span>nil</span> {</span><br><span>        <span>return</span> <span>false</span></span><br><span>    }</span><br><span>    <span>return</span> tp.FirstName != <span>""</span> &amp;&amp; tp.LastName != <span>""</span></span><br><span>}</span><br></pre></td></tr></table></figure><p>Now, we&rsquo;re checking for a nil interface in <code>PrintIfTrue</code> and for a nil pointer in <code>func (tp *TruthyPerson) IsTrue() bool</code>. And it will now print &ldquo;It&rsquo;s not true&rdquo;. We can see all this code <a href="https://go.dev/play/p/IihhiSXMm-q" rel="noopener noreferrer" target="_blank">working here</a>.</p><h2><a href="https://tt-rss.goodevilgenius.org/#bonus-check-for-both-nils-at-once-with-reflection" title="Bonus: Check for both nils at once with reflection" rel="noopener noreferrer" target="_blank"></a>Bonus: Check for both nils at once with reflection</h2><p>With reflection, we can make a small change to <code>PrintIfTrue</code> so that it can check for both nil interfaces and nil pointers. Here&rsquo;s the code:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br></pre></td><td><pre><span><span><span>func</span> <span>PrintIfTrue</span><span>(tg TruthGetter)</span></span> {</span><br><span><span>if</span> tg == <span>nil</span> {</span><br><span>fmt.Println(<span>"I can't tell if it's true"</span>)</span><br><span><span>return</span></span><br><span>}</span><br><span></span><br><span>val := reflect.ValueOf(tg)</span><br><span>k := val.Kind()</span><br><span><span>if</span> (k == reflect.Pointer || k == reflect.Chan || k == reflect.Func || k == reflect.Map || k == reflect.Slice) &amp;&amp; val.IsNil() {</span><br><span>fmt.Println(<span>"I can't tell if it's true"</span>)</span><br><span><span>return</span></span><br><span>}</span><br><span></span><br><span><span>if</span> tg.IsTrue() {</span><br><span>fmt.Println(<span>"It's true"</span>)</span><br><span>} <span>else</span> {</span><br><span>fmt.Println(<span>"It's not true"</span>)</span><br><span>}</span><br><span>}</span><br></pre></td></tr></table></figure><p>Here, we check for the nil interface first, as before. Next, we use reflection to get the type. <code>chan</code>, <code>func</code>, <code>map</code>, and <code>slice</code> can also be nil, in addition to pointers, so we check if the value is one of those types, and if so, check if it&rsquo;s nil. And if it is, we also return the &ldquo;I can&rsquo;t tell if it&rsquo;s true&rdquo; message. This may or may not be exactly what you want, but it&rsquo;s an option. With this change, we can do this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></pre></td><td><pre><span><span><span>func</span> <span>main</span><span>()</span></span> {</span><br><span>    <span>var</span> tp *TruthyPerson</span><br><span>    PrintIfTrue(tp)</span><br><span>}</span><br></pre></td></tr></table></figure><p>You might sometimes see a suggestion to something simpler, like:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></pre></td><td><pre><span><span>// Don't do this</span></span><br><span><span>if</span> tg == <span>nil</span> &amp;&amp; reflect.ValueOf(tg).IsNil() {</span><br><span>    fmt.Println(<span>"I can't tell if it's true"</span>)</span><br><span>    <span>return</span></span><br><span>}</span><br></pre></td></tr></table></figure><p>There are two reasons this doesn&rsquo;t work well. First, is that there is a performance overhead when using reflection. If you can avoid using reflection, you probably should. If we check for the nil interface first, we don&rsquo;t have to use reflection if it&rsquo;s a nil interface.</p><p>The second reason is the <code>reflect.Value.IsNil()</code> will panic if the type of the value isn&rsquo;t a type that can be nil. That&rsquo;s why we add in the check for the kind. If we hadn&rsquo;t checked the Kind, then we would&rsquo;ve gotten a panic on the <code>Truthy</code> and <code>TruthyNumber</code> types.</p><p>So, as long as we ensure we check the kind first, this will now print &ldquo;I can&rsquo;t tell if it&rsquo;s true&rdquo;, instead of &ldquo;It&rsquo;s not true&rdquo;. Depending on your perspective, this may be an improvement. <a href="https://go.dev/play/p/mL048k4C1be" rel="noopener noreferrer" target="_blank">Here</a> is the complete code with this change.</p>]]></content>
	<updated>2024-06-25T16:32:16+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://goodevilgenius.org</id>
		<link rel="self" href="https://goodevilgenius.org"/>
		<updated>2024-06-25T16:32:16+00:00</updated>
		<title>Dan's Musings</title></source>

	<category term="go"/>

	<category term="golang"/>

	<category term="programming"/>

	<category term="webdev"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2022-09-13:/436014</id>
	<link href="https://www.commitstrip.com/2022/09/13/once-upon-a-teams-meeting/" rel="alternate" type="text/html"/>
	<title type="html">Once upon a Teams meeting…</title>
	<summary type="html"><![CDATA[]]></summary>
	<content type="html"><![CDATA[<a href="https://www.commitstrip.com/wp-content/uploads/2022/09/Strip-Conf-Teams-sans-%C3%A9couter-650-finalenglish.jpg" rel="noopener noreferrer" target="_blank"><img src="https://www.commitstrip.com/wp-content/uploads/2022/09/Strip-Conf-Teams-sans-%C3%A9couter-650-finalenglish.jpg" alt="" referrerpolicy="no-referrer"></a>]]></content>
	<updated>2022-09-13T15:30:09+00:00</updated>
	<author><name>CommitStrip</name></author>
	<source>
		<id>https://www.commitstrip.com/</id>
		<link rel="self" href="https://www.commitstrip.com/"/>
		<updated>2022-09-13T15:30:09+00:00</updated>
		<title>CommitStrip - Blog relating the daily life of web agencies developers</title></source>

	<category term="t:1"/>


	<link rel="enclosure" 
		type="" 
		length="1"
		href="https://www.commitstrip.com/wp-content/uploads/2022/09/HeadlineImageTemplate-.jpg"/>

	<link rel="enclosure" 
		type="image/jpeg" 
		length="1"
		href="https://www.commitstrip.com/wp-content/uploads/2022/09/HeadlineImageTemplate-.jpg"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2022-09-13:/436013</id>
	<link href="https://goodevilgenius.org/2022/09/13/Accessing-Private-Properties-in-PHP-without-Reflection/" rel="alternate" type="text/html"/>
	<title type="html">Accessing Private Properties in PHP without Reflection</title>
	<summary type="html"><![CDATA[<p>I was discussing this technique during a code review recently, and I realized this cool technique mi...</p>]]></summary>
	<content type="html"><![CDATA[<p>I was discussing this technique during a code review recently, and I realized this cool technique might not be well known. I discovered it quite by accident a few years ago.</p><p>Sometimes, <em>strictly for testing purposes</em>, we might need to access a private or protected property or method. Our usual inclination is to use Reflection to do this. Reflection is a bit cumbersome, though, because there&rsquo;s a lot of extra boilerplate to set it up.</p><p>But closures actually provide us with a cool way to do that much more easily. If you&rsquo;ve ever used JavaScript&rsquo;s <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind" rel="noopener noreferrer" target="_blank"><code>bind</code></a> method, we can do the same thing in PHP.</p><p>Let&rsquo;s start with a sample class:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></pre></td><td><pre><span><span><span>class</span> <span>Person</span></span></span><br><span><span></span>{</span><br><span>    <span>public</span> <span>string</span> <span>$name</span> = <span>'Dan'</span>;</span><br><span>    <span>protected</span> <span>int</span> <span>$age</span>  = <span>40</span>;</span><br><span>    <span>private</span> <span>bool</span> <span>$cool</span>  = <span>true</span>;</span><br><span>    </span><br><span>    <span>protected</span> <span>static</span> <span>string</span> <span>$species</span> = <span>'homo sapien'</span>;</span><br><span>    </span><br><span>    <span>protected</span> <span>static</span> <span><span>function</span> <span>evolve</span>(<span></span>): <span>void</span></span></span><br><span><span>    </span>{</span><br><span>        <span>static</span>::<span>$species</span> = <span>'homo superior'</span>;</span><br><span>    }</span><br><span>    </span><br><span>    <span>protected</span> <span><span>function</span> <span>birthday</span>(<span></span>): <span>void</span></span></span><br><span><span>    </span>{</span><br><span>        <span>$this</span>-&gt;age++;</span><br><span>    }</span><br><span>    </span><br><span>    <span>public</span> <span><span>function</span> <span>howOldAmI</span>(<span></span>): <span>int</span></span></span><br><span><span>    </span>{</span><br><span>        <span>return</span> <span>$this</span>-&gt;age;</span><br><span>    }</span><br><span>}</span><br><span></span><br><span><span>$me</span> = <span>new</span> <span>Person</span>();</span><br></pre></td></tr></table></figure><h2><a href="https://tt-rss.goodevilgenius.org/#non-static-properties" title="Non-static properties" rel="noopener noreferrer" target="_blank"></a>Non-static properties</h2><p>We know how to read and change <code>$me-&gt;name</code>. It&rsquo;s public, so there&rsquo;s no problem there.</p><p>Now, what if we need, somewhere in a test, to set the exact age. That&rsquo;s a protected property, so we can&rsquo;t just directly change it, but Closures can give us a way around that.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span>(<span><span>fn</span> (<span><span>int</span> <span>$newAge</span></span>) =&gt;</span> <span>$this</span>-&gt;age = <span>$newAge</span>)-&gt;<span>call</span>(<span>$me</span>, <span>30</span>); <span>// Changes $me-&gt;age to 30</span></span><br></pre></td></tr></table></figure><p>So, what did we do here? Let&rsquo;s break it down a bit.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$changeAge</span> = (<span><span>fn</span> (<span><span>int</span> <span>$newAge</span></span>) =&gt;</span> <span>$this</span>-&gt;age = <span>$newAge</span>);</span><br></pre></td></tr></table></figure><p>First, we create a closure that changes <code>$this-&gt;age</code> to whatever is passed to it. If we tried to do <code>$changeAge(30)</code>, we&rsquo;d get an error, though, because we defined this outside of the class. <code>$this</code> doesn&rsquo;t actually exist here, or, if we put this code in a unit test, <code>$this</code> would refer to the unit test class itself.</p><p>That&rsquo;s where <code>call</code> comes in. <a href="https://www.php.net/manual/en/closure.call.php" rel="noopener noreferrer" target="_blank"><code>Closure::call</code></a> binds a closure to a new object, and calls it with whatever args you passed to it. In other words, the first argument passed to <code>call</code> become the <code>$this</code> within the closure. Any other arguments are passed to the function itself.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$changeAge</span>-&gt;<span>call</span>(<span>$me</span>, <span>30</span>);</span><br></pre></td></tr></table></figure><p>We can also simply access the <code>$age</code> and <code>$cool</code> properties without changing them. So, I can do:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br></pre></td><td><pre><span><span>$amICool</span> = (<span><span>fn</span> (<span></span>) =&gt;</span> <span>$this</span>-&gt;cool)-&gt;<span>call</span>(<span>$me</span>); <span>// true</span></span><br><span><span>$myAge</span>   = (<span><span>fn</span> (<span></span>) =&gt;</span> <span>$this</span>-&gt;age)-&gt;<span>call</span>(<span>$me</span>);</span><br></pre></td></tr></table></figure><h2><a href="https://tt-rss.goodevilgenius.org/#static-properties" title="Static properties" rel="noopener noreferrer" target="_blank"></a>Static properties</h2><p>That works great for the non-static properties or methods. What about the static ones?</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br></pre></td><td><pre><span><span>$mySpecies</span> = (<span><span>fn</span> (<span></span>) =&gt;</span> <span>static</span>::<span>$species</span>)-&gt;<span>bindTo</span>(<span>null</span>, <span>Person</span>::<span>class</span>)(); <span>// homo sapien</span></span><br><span>(<span><span>fn</span> (<span></span>) =&gt;</span> <span>static</span>::<span>evolve</span>())-&gt;<span>bindTo</span>(<span>null</span>, <span>Person</span>::<span>class</span>)(); <span>// Changes to homo superior</span></span><br><span>(<span><span>fn</span> (<span><span>string</span> <span>$newSpecies</span></span>) =&gt;</span> <span>static</span>::<span>$species</span> = <span>$newSpecies</span>)-&gt;<span>bindTo</span>(<span>null</span>, <span>Person</span>::<span>class</span>)(<span>'homo erectus'</span>); <span>// devolve to homo erectus</span></span><br></pre></td></tr></table></figure><p>So, this is a little different. First, we&rsquo;re using <a href="https://www.php.net/manual/en/closure.bindto.php" rel="noopener noreferrer" target="_blank"><code>Closure::bindTo</code></a> which returns a new closure, which has been re-bound to the specified object or class. With <code>bindTo</code>, you can pass an object (like <code>call</code>), or you can leave that null and pass a class instead, which changes the static binding. That&rsquo;s what we&rsquo;ve done here. And since it returns a new closure, you then have to call it, which is why we have an extra <code>()</code> after.</p><p>So, let&rsquo;s break this down step-by-step as well.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$getSpecies</span> = (<span><span>fn</span> (<span></span>) =&gt;</span> <span>static</span>::<span>$species</span>);</span><br></pre></td></tr></table></figure><p>A closure which returns that <code>$species</code> of the current scope.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$boundGetSpecies</span> = <span>$getSpecies</span>-&gt;<span>bindTo</span>(<span>null</span>, <span>Person</span>::<span>class</span>);</span><br></pre></td></tr></table></figure><p>A new closure, which changes the scope to <code>Person</code>, meaning any references to <code>static</code> refer to <code>Person</code>.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$mySpecies</span> = <span>$boundGetSpecies</span>();</span><br></pre></td></tr></table></figure><p>Or, if we to change the species to an arbitrary value, as we did in the third one:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></pre></td><td><pre><span><span>// Create species changing closure, initially bound to the current scope</span></span><br><span><span>$changeSpecies</span> = (<span><span>fn</span> (<span><span>string</span> <span>$newSpecies</span></span>) =&gt;</span> <span>static</span>::<span>$species</span> = <span>$newSpecies</span>);</span><br><span><span>// Create a new Closure, bound to the Person scope</span></span><br><span><span>$changePersonSpecies</span> = <span>$changeSpecies</span>-&gt;<span>bindTo</span>(<span>null</span>, <span>Person</span>::<span>class</span>);</span><br><span><span>// Change it to whatever</span></span><br><span><span>$changePersonSpecies</span>(<span>'homo erectus'</span>);</span><br></pre></td></tr></table></figure><h2><a href="https://tt-rss.goodevilgenius.org/#using-bindto-with-an-object" title="Using bindTo with an object" rel="noopener noreferrer" target="_blank"></a>Using <code>bindTo</code> with an object</h2><p>You can use <code>bindTo</code> in a similar way to <code>call</code>.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span>(<span><span>fn</span> (<span></span>) =&gt;</span> <span>$this</span>-&gt;<span>birthday</span>())-&gt;<span>bindTo</span>(<span>$me</span>)();</span><br></pre></td></tr></table></figure><p>Let&rsquo;s break it down.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$haveABirthday</span> = (<span><span>fn</span> (<span></span>) =&gt;</span> <span>$this</span>-&gt;<span>birthday</span>());</span><br></pre></td></tr></table></figure><p>Closure which calls <code>birthday()</code> on the object within the current scope.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$haveMyBirthday</span> = <span>$haveABirthday</span>-&gt;<span>bindTo</span>(<span>$me</span>);</span><br></pre></td></tr></table></figure><p>Create a new closure with <code>$me</code> as the scope. This way, any reference to <code>$this</code> refers to <code>$me</code>.</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>$haveMyBirthday</span>();</span><br></pre></td></tr></table></figure><p>Since <code>$haveMyBirthday</code> is a closure, we have to actually call it.</p><h2><a href="https://tt-rss.goodevilgenius.org/#conclusion" title="Conclusion" rel="noopener noreferrer" target="_blank"></a>Conclusion</h2><p>Sure, we can use <a href="https://www.php.net/manual/en/class.reflectionclass.php" rel="noopener noreferrer" target="_blank"><code>ReflectionClass</code></a> and <a href="https://www.php.net/manual/en/class.reflectionobject.php" rel="noopener noreferrer" target="_blank"><code>ReflectionObject</code></a> to do all of this, but this technique greatly simplifies it, since calling a single private method is a single line of code.</p><p>I actually use this in a <a href="https://packagist.org/packages/danjones000/object-spy" rel="noopener noreferrer" target="_blank">package I recently wrote</a> that should make this even easier.</p><p>And to reiterate, I do not recommend doing this in production code. There&rsquo;s a reason visibility exists. We shouldn&rsquo;t circumvent it like this in code on our server. But, if we need to fiddle around with some objects for testing, this technique can simplify that for us.</p>]]></content>
	<updated>2024-06-25T16:32:16+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://goodevilgenius.org</id>
		<link rel="self" href="https://goodevilgenius.org"/>
		<updated>2024-06-25T16:32:16+00:00</updated>
		<title>Dan's Musings</title></source>

	<category term="php"/>

	<category term="programming"/>

	<category term="webdev"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2022-07-28:/435059</id>
	<link href="https://languagehat.com/more-on-fillers/" rel="alternate" type="text/html"/>
	<title type="html">More on Fillers.</title>
	<summary type="html"><![CDATA[<p>We&rsquo;ve discussed filler words a number of times (2009, 2017, 2021), but Anne Delaney&rsquo;s JS...</p>]]></summary>
	<content type="html"><![CDATA[<p>We&rsquo;ve discussed filler words a number of times (<a href="https://languagehat.com/fillers-around-the-world/" rel="noopener noreferrer" target="_blank">2009</a>, <a href="https://languagehat.com/the-mystery-of-fillers/" rel="noopener noreferrer" target="_blank">2017</a>, <a href="https://languagehat.com/so-right/" rel="noopener noreferrer" target="_blank">2021</a>), but Anne Delaney&rsquo;s <a href="https://daily.jstor.org/filler-words-floor-holders-the-sounds-our-thoughts-make/" rel="noopener noreferrer" target="_blank">JSTOR Daily piece</a> has some things that weren&rsquo;t in the previous posts, and I thought it was worth bringing to your attention.  Some excerpts:</p>
<blockquote><p>In spoken language, we see that many elements are universal, one being the way speakers listen and <a href="https://www.jstor.org/stable/4168983?mag=filler-words-floor-holders-the-sounds-our-thoughts-make" rel="noopener noreferrer" target="_blank">take turns in a conversation</a>. These markers or thinking sounds (<em>uh, uh huh, huh, hmm, er, like, right?</em>) may be collections of sounds with meaningless lexical value, yet they pack a pragmatic punch.</p>
<p>They can be perceived with a range of filters, neutral or positive ones such as creating connection, agreement, and unity; or with a more negative view, such as a crutch, tic, parasitic word, or distracting habit. These exist in every language. The French utter <em>eh bien</em>; Portuguese have <em>ent&atilde;o, ta, pois</em>; Japanese &#12360;&#12540;&#12392; (&ldquo;<em>eeto</em>&rdquo;), and &#12394;&#12435;&#12363; (&ldquo;<em>nanka</em>&rdquo;); Spanish &ndash; <em>mira, vale</em>, among others.</p>
<p> Understood by several labels, verbal fillers and hesitation markers are some of these universal elements, a type of discourse marker. Interjections and rejoinders also come to mind&mdash;words a listener uses to keep the conversation going and show the speaker she understands and even sympathizes with them; for language learners, using them adeptly (with the right syntactic placement, intonation, and timing) may demonstrate further fluency. [&hellip;]</p></blockquote>
<p><span></span></p>
<blockquote><p>When the speaker is not visible, verbal &ldquo;signposts,&rdquo; can be helpful, as they take the role, in part, that nonverbals do. Politicians, public speakers, and those making a formal presentation, on the other hand, might gather their thoughts and hold the floor with a &ldquo;Soooo&hellip;,&rdquo; a &ldquo;Look,&rdquo; or an &ldquo;Ummm&rdquo; in between points. Learners of more than one language might hear these signals early on, and start incorporating them, purposefully or not, to create added fluency and confidence in the new target language.</p>
<p>Judgments of and reactions to these elements of speech range from perceiving a mark of personality&mdash;it&rsquo;s simply what the speaker does&mdash;to a more visceral judgment (distracting, inept, unprofessional, unpracticed). Examples of these utterances include the well-known &ldquo;you know&rdquo; and &ldquo;like,&rdquo; and the increasingly more frequent &ldquo;right?&rdquo;</p>
<p><em>You know</em> creates an exchange structure focusing the listener&rsquo;s attention on a specific piece of information provided by the speaker. <em>That seems like a lot to pay for a coffee, you know?</em></p>
<p><em>Like</em> is a big one for protecting oneself from potential disagreement: &ldquo;Do you, like, want to have Indian food for lunch?&rdquo; perhaps really means: &ldquo;Will you have Indian food for lunch with me?,&rdquo; or functions to reduce uncertainty and perhaps avoid rebuttal or being wrong in an utterance:</p>
<p><em>It&rsquo;s, like, supposed to rain all afternoon.</em></p>
<p><em>Right</em>: Has this one crept in quickly or slowly? The peppering of a &ldquo;right(?)&rdquo; in the telling of a story or sharing new information can take the place of &ldquo;Do you know what I mean?&rdquo; Additionally, depending on placement and tone, it can connote a spoken yet implied request that the listener agree, while the listener might be thinking, &ldquo;Must I agree? (I don&rsquo;t, actually: I just learned this; this is his story; or I don&rsquo;t agree at all.&rdquo;) [&hellip;]</p>
<p>Imagine how challenging it is to use verbal fillers accurately in a second or third language. Or, um, rather, since there is already a more spacious canvas, maybe one should not even bother sprinkling them in. Indeed, interpreters are trained to omit fillers, so as not to be perceived as uncertain or create doubt with the accuracy of an interpretation. Ironically, using filled pauses naturally, &ldquo;accurately,&rdquo; where they are expected in conversation, can result in the speaker being perceived as fluent, or at least communicatively competent in a second or third language. [&hellip;]</p>
<p>Mark Liberman discovered, though his <a href="https://www.theatlantic.com/health/archive/2014/08/men-say-uh-and-women-say-um/375729/" rel="noopener noreferrer" target="_blank">parsing</a> of 14,000 phone conversations, that &ldquo;uh&rdquo; increases with age, but that at every age, male speakers use it more than female speakers do; whereas, &ldquo;um&rdquo; decreases with age, but female speakers use it more than male ones do at each stage in life.</p>
<p>The state of affairs is actually simple and one of a common ground: we each have our own idiolect, a term coined by <a href="https://www.jstor.org/stable/410284?mag=filler-words-floor-holders-the-sounds-our-thoughts-make" rel="noopener noreferrer" target="_blank">Bernard Bloch</a> from the Greek <em>idio-</em> (personal, distinct) + <em>-lect</em> (social variety of a language), which is the unique speech of an individual. This term refers to the theory that, while being influenced by or sharing a common dialect, sociolect, culture, and environment, no two individuals have the exact same linguistic tastes and idiosyncratic features in their personal inventory of variants.</p></blockquote>
<p>Obligatory gripe: I&rsquo;m tired of the ubiquitous &ldquo;right?&rdquo; and wish it would go away.  Thanks, Martin!</p>]]></content>
	<updated>2022-07-28T20:49:10+00:00</updated>
	<author><name>languagehat</name></author>
	<source>
		<id>http://languagehat.com</id>
		<link rel="self" href="http://languagehat.com"/>
		<updated>2022-07-28T20:49:10+00:00</updated>
		<title>languagehat.com</title></source>

	<category term="uncategorized"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2022-06-21:/434228</id>
	<link href="https://goodevilgenius.org/2022/06/21/Use-Laravel-Events-like-Wordpress-Hooks/" rel="alternate" type="text/html"/>
	<title type="html">Use Laravel Events like Wordpress Hooks</title>
	<summary type="html"><![CDATA[<p>Many web developers have worked with Wordpress at least somewhat. If you&rsquo;ve doneany plugin or theme ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Many web developers have worked with Wordpress at least somewhat. If you&rsquo;ve doneany plugin or theme development, there&rsquo;s a good chance you&rsquo;ve used<a href="https://developer.wordpress.org/plugins/hooks/" rel="noopener noreferrer" target="_blank">Wordpress hooks</a> at some point.</p><p>If you&rsquo;ve called <code>add_action</code> or <code>add_filter</code>, you&rsquo;ve used a Wordpress hook. Ifyou&rsquo;ve called <code>do_action</code> or <code>appy_filters</code>, you&rsquo;ve created your own hook.</p><p>If you&rsquo;ve spent some time working with Wordpress, and are now working withLaravel, you might be wondering if Laravel has a hook system as well. You&rsquo;re inluck, using Laravel&rsquo;s <a href="https://laravel.com/docs/9.x/events" rel="noopener noreferrer" target="_blank">event system</a>, you can do the same sort of thingsthat Wordpress&rsquo;s hook system can do, and we&rsquo;re going go over how to do that witha few code samples.</p><p>First off, lets talk about hooks in Wordpress.</p><h2><a href="https://tt-rss.goodevilgenius.org/#wordpress-hooks" title="Wordpress hooks" rel="noopener noreferrer" target="_blank"></a>Wordpress hooks</h2><p>In Wordpress, <a href="https://developer.wordpress.org/plugins/hooks/" rel="noopener noreferrer" target="_blank">hooks</a> are a system that allow the developer to <em>hook</em> into anevent, and add some functionality. Hooks come in two varieties: actions, andfilters. Both types do essentially the same thing: when something happens in thesystem (or is about to happen), the action/filter is called. A developerbuilding a plugin or theme can add functions that run on these events.</p><p>The difference between these two types of hooks have to do with the data ispassed to the function, and the data that is returned from the function.</p><p>An action, is a function that returns nothing. In fact, if you add an actionthat returns a value, that value is ignored, and nothing happens with it.</p><p>A common example of an action is the built-in <code>init</code> action. This action is runearly on in the request lifecycle. One possible use is by theme developers whenregistering custom post types.</p><p>This might look like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br></pre></td><td><pre><span><span>add_action</span>( <span>'init'</span>, function () {</span><br><span>    <span>register_post_type</span>( <span>'book'</span>, [ <span>'public'</span> =&gt; <span>true</span> ] );</span><br><span>} );</span><br></pre></td></tr></table></figure><p>A filter is a function that returns a value that is intended to modify whatevervalue is passed to it.</p><p>A good example of a filter is the <code>body_class</code> filter. The function is passed anarray of classes that are to be used in the <code>&lt;body class&gt;</code> attribute. Thefilter function should add to, or remove classes in the array, and then returnthe array.</p><p>This might look like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></pre></td><td><pre><span><span>add_filter</span>( <span>'body_class'</span>, function ( <span>array</span> <span>$classes</span> ) {</span><br><span>    <span>$classes</span>[] = <span>'i-love-hooks'</span>;</span><br><span>    </span><br><span>    <span>return</span> <span>$classes</span>;</span><br><span>} );</span><br></pre></td></tr></table></figure><p>Now that we&rsquo;ve got a refresher on Wordpress hooks, let&rsquo;s look at Laravel Events.</p><h2><a href="https://tt-rss.goodevilgenius.org/#laravel-events" title="Laravel Events" rel="noopener noreferrer" target="_blank"></a>Laravel Events</h2><p>In Laravel, <a href="https://laravel.com/docs/9.x/events" rel="noopener noreferrer" target="_blank">Events</a> serve the same purpose as Wordpress hooks: whensomething happens, an event is triggered, and the developer can respond to thatevent by performing some action. One important difference to remember betweenWordpress hooks and Laravel Events are that in Wordpress, a hook has a name, andthat&rsquo;s how you add your function to that hook, is by using the name. In Laravel,an event is an actual object which is instantiated. When you listen for thatevent (i.e., when you hook into it), you listen for events of a particularclassname. In this way, Laravel is using more object-oriented programming.</p><p>Laravel has a number of built-in events, and any developer can create their owncustom events. Let&rsquo;s look at how one might be used.</p><p>One event provided by Laravel is the <code>QueryExecuted</code> event. This event isexecuted whenever a database query is made. The event contains the query, andthe amount of time (in milliseconds) that the query took to execute.</p><p>In Wordpress, you would add your hook listener during you plugin init function,or in your theme&rsquo;s <code>functions.php</code> file, usually. In Laravel, normally, youregister an event listener in a <a href="https://laravel.com/docs/9.x/providers" rel="noopener noreferrer" target="_blank">service provider</a>. You can use any serviceprovider, by adding the following to the <code>boot</code> method:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></pre></td><td><pre><span><span>use</span> <span>Illuminate</span>\<span>Database</span>\<span>Events</span>\<span>QueryExecuted</span>;</span><br><span><span>use</span> <span>Illuminate</span>\<span>Support</span>\<span>Facades</span>\<span>Event</span>;</span><br><span><span>use</span> <span>App</span>\<span>Listeners</span>\<span>LogQueries</span>;</span><br><span></span><br><span><span>public</span> <span><span>function</span> <span>boot</span>(<span></span>) </span>{</span><br><span>    <span>Event</span>::<span>listen</span>(<span>QueryExecuted</span>::<span>class</span>, <span>LogQueries</span>::<span>class</span>);</span><br><span>}</span><br></pre></td></tr></table></figure><p>In this example, <code>LogQueries</code> is a listener class that contains a method calledeither <code>handle</code> or <code>__invoke</code>. This method will get an instance of the<code>QueryExecuted</code> class. It might look something like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></pre></td><td><pre><span><span>use</span> <span>Illuminate</span>\<span>Database</span>\<span>Events</span>\<span>QueryExecuted</span>;</span><br><span><span>use</span> <span>Illuminate</span>\<span>Support</span>\<span>Facades</span>\<span>Log</span>;</span><br><span></span><br><span><span><span>class</span> <span>LogQueries</span> </span>{</span><br><span>    <span>public</span> <span><span>function</span> <span>handle</span>(<span>QueryExecuted <span>$evt</span></span>) </span>{</span><br><span>        <span>Log</span>::<span>debug</span>(<span>"Executed <span>{$evt-&gt;sql}</span> in <span>{$evt-&gt;time}</span>ms"</span>);</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>You can also listen using a Closure, similar to the Wordpress examples I gavebefore. Something like this could be done in the service provider:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></pre></td><td><pre><span><span>use</span> <span>Illuminate</span>\<span>Database</span>\<span>Events</span>\<span>QueryExecuted</span>;</span><br><span><span>use</span> <span>Illuminate</span>\<span>Support</span>\<span>Facades</span>\<span>Event</span>;</span><br><span><span>use</span> <span>Illuminate</span>\<span>Support</span>\<span>Facades</span>\<span>Log</span>;</span><br><span></span><br><span><span>public</span> <span><span>function</span> <span>boot</span>(<span></span>) </span>{</span><br><span>    <span>Event</span>::<span>listen</span>(function (QueryExecuted <span>$evt</span>) {</span><br><span>        <span>Log</span>::<span>debug</span>(<span>"Executed <span>{$evt-&gt;sql}</span> in <span>{$evt-&gt;time}</span>ms"</span>);</span><br><span>    });</span><br><span>}</span><br></pre></td></tr></table></figure><p>There are other variations on how to do this. We won&rsquo;t be going into detail inthese. Feel free to consult the <a href="https://laravel.com/docs/9.x/events" rel="noopener noreferrer" target="_blank">official documentation</a>.</p><h2><a href="https://tt-rss.goodevilgenius.org/#real-life-sort-of-examples" title="Real-life (sort of) examples" rel="noopener noreferrer" target="_blank"></a>Real-life (sort of) examples</h2><p>For our examples, we&rsquo;re going to imagine we&rsquo;re building an e-book store usingLaravel. So, first, lets establish a few models that we&rsquo;ll be using.</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></pre></td><td><pre><span><span>namespace</span> <span>App</span>\<span>Models</span>;</span><br><span></span><br><span><span><span>class</span> <span>Customer</span> </span>{</span><br><span>    <span>// ...</span></span><br><span>}</span><br><span></span><br><span><span><span>class</span> <span>Book</span> </span>{</span><br><span>    <span>// ...</span></span><br><span>}</span><br><span></span><br><span><span><span>class</span> <span>Checkout</span> </span>{</span><br><span>    <span>public</span> <span><span>function</span> <span>customer</span>(<span></span>): <span>HasOne</span></span></span><br><span><span>    </span>{</span><br><span>        <span>return</span> <span>$this</span>-&gt;<span>hasOne</span>(<span>Customer</span>::<span>class</span>);</span><br><span>    }</span><br><span>    </span><br><span>    <span>public</span> <span><span>function</span> <span>books</span>(<span></span>): <span>BelongsToMany</span></span></span><br><span><span>    </span>{</span><br><span>        <span>return</span> <span>$this</span>-&gt;<span>belongsToMany</span>(<span>Book</span>::<span>class</span>);</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>We have customers, books, and for when they are making a purchase, checkouts.The checkout has a <a href="https://laravel.com/docs/9.x/eloquent-relationships" rel="noopener noreferrer" target="_blank">relationship</a> to the customer and the books in the cart.</p><h3><a href="https://tt-rss.goodevilgenius.org/#actions" title="Actions" rel="noopener noreferrer" target="_blank"></a>Actions</h3><p>As we discussed earlier, Wordpress has two types of hooks: actions and filters.It should be clear by now how to use Laravel Events in the same way as Wordpressactions. But, lets go over this using our store example.</p><p>Let&rsquo;s suppose our customer has already picked out their books, and added it tothe cart. We have our <code>Checkout</code> model ready, called <code>$checkout</code>, and thecustomer has just pressed the &ldquo;Check out&rdquo; button on the site. We go through somelogic to verify payment and shipping and whatnot, and everything&rsquo;s fine, so oncethat&rsquo;s all done, we can fire an event to indicate that checkout has completed.</p><p>We create this event with the command:</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span>php artisan make:even CheckoutComplete</span><br></pre></td></tr></table></figure><p>Our event class might look like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></pre></td><td><pre><span><span>use</span> <span>Illuminate</span>\<span>Foundation</span>\<span>Events</span>\<span>Dispatchable</span>;</span><br><span><span>use</span> <span>Illuminate</span>\<span>Queue</span>\<span>SerializesModels</span>;</span><br><span></span><br><span><span><span>class</span> <span>CheckoutComplete</span></span></span><br><span><span></span>{</span><br><span>    <span>use</span> <span>Dispatchable</span>, <span>SerializesModels</span>;</span><br><span></span><br><span>    <span>public</span> Checkout <span>$checkout</span>;</span><br><span></span><br><span>    <span>public</span> <span><span>function</span> <span>__construct</span>(<span>Checkout <span>$checkout</span></span>)</span></span><br><span><span>    </span>{</span><br><span>        <span>$this</span>-&gt;checkout = <span>$checkout</span>;</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>And then, we&rsquo;d dispatch it like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></pre></td><td><pre><span><span>use</span> <span>Illuminate</span>\<span>Support</span>\<span>Facades</span>\<span>Event</span>;</span><br><span><span>use</span> <span>App</span>\<span>Events</span>\<span>CheckoutComplete</span>;</span><br><span></span><br><span><span>Event</span>::<span>dispatch</span>(<span>new</span> <span>CheckoutComplete</span>(<span>$checkout</span>));</span><br></pre></td></tr></table></figure><p>All of this might be similar, in Wordpress to the following:</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>do_action</span>( <span>'checkout_complete'</span>, <span>$checkout</span> );</span><br></pre></td></tr></table></figure><p>Obviously the Wordpress code is much shorter, but the Laravel code gives us moretype-safety, and some more flexibility, since we can easily add whatever we wantto that event model.</p><h4><a href="https://tt-rss.goodevilgenius.org/#listening-for-the-event" title="Listening for the event" rel="noopener noreferrer" target="_blank"></a>Listening for the event</h4><p>All we&rsquo;ve done so far is create an event and dispatch it. Now we hook into it byusing a listener.</p><p>Let&rsquo;s suppose whenever a customer makes a purchase, they get one reward point forevery book they purchased, and the points are redeemable for discounts in thefuture.</p><p>So, we use our <code>CheckoutComplete</code> event and create a listener that awards thesepoints.</p><p>So, our listener can be as simple as:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></pre></td><td><pre><span><span>use</span> <span>App</span>\<span>Events</span>\<span>CheckoutComplete</span>;</span><br><span><span>use</span> <span>App</span>\<span>Service</span>\<span>RewardPoints</span>;</span><br><span></span><br><span><span><span>class</span> <span>GiveRewardPoints</span></span></span><br><span><span></span>{</span><br><span>    <span>protected</span> RewardPoints <span>$service</span>;</span><br><span></span><br><span>    <span>public</span> <span><span>function</span> <span>__construct</span>(<span>RewardPoints <span>$rewardsService</span></span>)</span></span><br><span><span>    </span>{</span><br><span>        <span>$this</span>-&gt;service = <span>$rewardsService</span>;</span><br><span>    }</span><br><span></span><br><span>    <span>public</span> <span><span>function</span> <span>handle</span>(<span>CheckoutComplete <span>$checkout</span></span>)</span></span><br><span><span>    </span>{</span><br><span>        <span>$service</span>-&gt;<span>award</span>(<span>$checkout</span>-&gt;customer, <span>$checkout</span>-&gt;<span>books</span>()-&gt;<span>count</span>());</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>We register this listener, as we explained above, in a service provider like:</p><figure><table><tr><td><pre><span>1</span><br></pre></td><td><pre><span><span>Event</span>::<span>listen</span>(<span>CheckoutComplete</span>::<span>class</span>, <span>GiveRewardPoints</span>::<span>class</span>);</span><br></pre></td></tr></table></figure><p>You can register as many listeners for an event as you like.</p><p>All of this might be something like the following in Wordpress.</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></pre></td><td><pre><span><span><span>function</span> <span>give_reward_points</span>(<span> <span>array</span> <span>$checkout</span> </span>) </span>{</span><br><span>    <span>$rewardsService</span>-&gt;<span>award</span>( <span>$checkout</span>[<span>'customer_id'</span>], <span>count</span>( <span>$checkout</span>[<span>'books'</span>] ) );</span><br><span>}</span><br><span></span><br><span><span>add_action</span>( <span>'checkout_complete'</span>, <span>'give_reward_points'</span> );</span><br></pre></td></tr></table></figure><h3><a href="https://tt-rss.goodevilgenius.org/#filters" title="Filters" rel="noopener noreferrer" target="_blank"></a>Filters</h3><p>Actions are easy, but filters are a little less obvious. To understand how wecan use events like Wordpress filters, we need to note that since an event is anobject with values on it, we can modify those values on the event. This gives usmuch more flexibility than Wordpress filters, which only give us the option ofreturning a new value.</p><p>For our example, let&rsquo;s suppose our customer has just entered their addressinformation during checkout. We might need to do some validation on thataddress. Normally in Laravel, you would probably throw a <code>ValidationException</code>in this sort of case, but let&rsquo;s try something a little different.</p><p>First, we&rsquo;ll create a <code>CheckoutAddressEntered</code> event:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></pre></td><td><pre><span><span><span>class</span> <span>CheckoutAddressEntered</span></span></span><br><span><span></span>{</span><br><span>    <span>public</span> Checkout <span>$checkout</span>;</span><br><span>    <span>public</span> Address <span>$address</span>;</span><br><span>    <span>public</span> <span>array</span> <span>$errors</span> = [];</span><br><span>}</span><br></pre></td></tr></table></figure><p>We&rsquo;ll suppose that the <code>$address</code> object is some sort of data transfer objectthat just holds the address information. The implementation isn&rsquo;t important forour example.</p><p>Now, in our controller, we might have some logic like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></pre></td><td><pre><span><span>$addressEntered</span> = <span>new</span> <span>CheckoutAddressEntered</span>();</span><br><span><span>$addressEntered</span>-&gt;checkout = <span>$checkout</span>;</span><br><span><span>$addressEntered</span>-&gt;address = <span>$address</span>;</span><br><span></span><br><span><span>Event</span>::<span>dispatch</span>(<span>$addressEntered</span>);</span><br><span></span><br><span><span>if</span> (<span>count</span>(<span>$addressEntered</span>-&gt;errors)) {</span><br><span>    <span>return</span> <span>new</span> <span>JsonResponse</span>([<span>'errors'</span> =&gt; <span>$addressEntered</span>-&gt;errors]);</span><br><span>}</span><br><span></span><br><span><span>return</span> <span>new</span> <span>JsonResponse</span>([<span>'data'</span> =&gt; <span>$checkout</span>]);</span><br></pre></td></tr></table></figure><p>So, what did we do here exactly? We don&rsquo;t have any errors on <code>$addressEntered</code>here. Let&rsquo;s check out our listener.</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></pre></td><td><pre><span><span><span>class</span> <span>ValidateAddress</span></span></span><br><span><span></span>{</span><br><span>    <span>public</span> <span><span>function</span> <span>__construct</span>(<span><span>protected</span> AddressValidator <span>$validator</span></span>) </span>{ <span>/**/</span> }</span><br><span>    </span><br><span>    <span>public</span> <span><span>function</span> <span>handle</span>(<span>CheckoutAddressEntered <span>$evt</span></span>)</span></span><br><span><span>    </span>{</span><br><span>        <span>if</span> (!<span>$validator</span>-&gt;<span>validate</span>(<span>$evt</span>-&gt;address)) {</span><br><span>            <span>$evt</span>-&gt;errors[] = <span>'Address validation failed'</span>;</span><br><span>        }</span><br><span>    }</span><br><span>}</span><br></pre></td></tr></table></figure><p>So, in our listener, we validated the address, and if it failed, we appended tothe <code>errors</code> array with a failure message. When we called <code>Event::dispatch</code>, ourlisteners were run with the same <code>$addressEntered</code> object being passed to thelistener. Each non-queued listener is run before <code>Event::dispatch</code> finishes, so,any changes made to the event are carried over.</p><p>If we were to implement this in Wordpress, it might look like this:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></pre></td><td><pre><span><span>$errors</span> = <span>apply_filters</span>( <span>'checkout_address_entered'</span>, [], <span>$address</span>, <span>$checkout</span> );</span><br><span><span>if</span> ( <span>count</span>( <span>$errors</span> ) ) {</span><br><span>    <span>wp_send_json_error</span>( <span>$errors</span>, <span>400</span> );</span><br><span>} <span>else</span> {</span><br><span>    <span>wp_send_json_success</span>( <span>$checkout</span> );</span><br><span>}</span><br></pre></td></tr></table></figure><p>And then, our callback would be:</p><figure><table><tr><td><pre><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></pre></td><td><pre><span><span><span>function</span> <span>validate_address</span>(<span> <span>array</span> <span>$errors</span>, <span>array</span> <span>$address</span>, <span>array</span> <span>$checkout</span> </span>) </span>{</span><br><span>    <span>if</span> ( ! <span>$addressValidator</span>-&gt;<span>validate</span>( <span>$address</span> ) ) {</span><br><span>        <span>$errors</span>[] = <span>'Address validation failed'</span>;</span><br><span>    }</span><br><span>}</span><br><span></span><br><span><span>add_filter</span>( <span>'checkout_address_entered'</span>, <span>'validate_address'</span>, <span>10</span>, <span>3</span> );</span><br></pre></td></tr></table></figure><p>We might have another listener that checks that all of the books purchased canbe legally sold in the customer&rsquo;s country.</p><p>One <strong>very important thing to note</strong> is that for this to work properly, thelistener <em>cannot</em> be a <a href="https://laravel.com/docs/9.x/events#queued-event-listeners" rel="noopener noreferrer" target="_blank">queued listener</a>. If it is, thevalidation will happen outside of the request, after the response has alreadybeen sent to the client. If you used the <code>make:listener</code> artisan command tocreate the listener class, you&rsquo;ll need to remove the <code>ShouldQueue</code> trait fromthat class.</p><h2><a href="https://tt-rss.goodevilgenius.org/#conclusion" title="Conclusion" rel="noopener noreferrer" target="_blank"></a>Conclusion</h2><p>If you&rsquo;re coming from Wordpress to Laravel, there are a lot of new things tolearn. With hooks being such an integral part of Wordpress, hopefully learninghow Laravel events work will help make that transition.</p>]]></content>
	<updated>2024-06-25T16:32:16+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://goodevilgenius.org</id>
		<link rel="self" href="https://goodevilgenius.org"/>
		<updated>2024-06-25T16:32:16+00:00</updated>
		<title>Dan's Musings</title></source>

	<category term="laravel"/>

	<category term="programming"/>

	<category term="webdev"/>

	<category term="wordpress"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2022-02-01:/430756</id>
	<link href="https://www.dudeiwantthat.com/outdoors/camping/monolith-tent-hanging-shelter.asp" rel="alternate" type="text/html"/>
	<title type="html">MONOLITH Tent &amp; Hanging Shelter</title>
	<summary type="html"><![CDATA[<p>Be the pillar of your next camping trip with the MONOLITH tent and hanging shelter. Especially if yo...</p>]]></summary>
	<content type="html"><![CDATA[<p>Be the pillar of your next camping trip with the MONOLITH tent and hanging shelter. Especially if you prefer to sleep alone, or no one wants to share with you after you become Snorey McSnorerson from some expedition whiskey, or Farty McFarterson...</p>]]></content>
	<updated>2022-02-01T15:06:00+00:00</updated>
	<author><name>info@dudeiwantthat.com Erin Carstens</name></author>
	<source>
		<id>http://www.dudeiwantthat.com</id>
		<link rel="self" href="http://www.dudeiwantthat.com"/>
		<updated>2022-02-01T15:06:00+00:00</updated>
		<title>DudeIWantThat.com</title></source>

	<category term="black"/>

	<category term="camping"/>

	<category term="expedition"/>

	<category term="hammock"/>

	<category term="mountaineering"/>

	<category term="shelter"/>

	<category term="tent"/>


	<link rel="enclosure" 
		type="image/jpeg" 
		length="1000"
		href="https://static.dudeiwantthat.com/img/outdoors/camping/monolith-tent-hanging-53462.jpg"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2021-10-27:/428182</id>
	<link href="https://cheerupemokid.com/comic/forehead/" rel="alternate" type="text/html"/>
	<title type="html">Forehead</title>
	<summary type="html"><![CDATA[]]></summary>
	<content type="html"><![CDATA[]]></content>
	<updated>2021-10-27T11:30:22+00:00</updated>
	<author><name>EnzoComics</name></author>
	<source>
		<id>http://www.cheerupemokid.com</id>
		<link rel="self" href="http://www.cheerupemokid.com"/>
		<updated>2021-10-27T11:30:22+00:00</updated>
		<title>Cheer Up, Emo Kid</title></source>

	<category term="background joke"/>

	<category term="cats"/>

	<category term="has bonus panel"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2021-07-14:/424757</id>
	<link href="https://goodevilgenius.org/2021/07/14/Healthcare-and-Employment/" rel="alternate" type="text/html"/>
	<title type="html">Healthcare and Employment</title>
	<summary type="html"><![CDATA[<p>I&rsquo;m lucky to have a job that provides good benefits, including good healthcare for myself and my fam...</p>]]></summary>
	<content type="html"><![CDATA[<p>I&rsquo;m lucky to have a job that provides good benefits, including good healthcare for myself and my family.</p><p>But every time I decide to switch jobs, my first question about a potential new job is about what sort of health insurance benefits are provided.</p><p>And even when they do provide good insurance, I always have to ask a bunch of questions about availability and so on to ensure that I won&rsquo;t go without coverage during the period that I&rsquo;m switching from one job to another.</p><p>The fact that this is such a top concern when trying to decide where I&rsquo;m going to work is absurd.</p><p>I don&rsquo;t really understand the history of healthcare in the United States, so I just don&rsquo;t understand how we came to be in a place where adequate healthcare become tied to employment.</p><p>And I really don&rsquo;t understand the people that think that this is a reasonable way to do this. Why can&rsquo;t we fix this, and just provide affordable healthcare to everybody? Why aren&rsquo;t people willing to pay a little more in taxes so that nobody has to worry about whether or not they can afford to go to the doctor or get the medicines they need?</p><p>I just don&rsquo;t get it.</p>]]></content>
	<updated>2024-06-25T16:32:16+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://goodevilgenius.org</id>
		<link rel="self" href="https://goodevilgenius.org"/>
		<updated>2024-06-25T16:32:16+00:00</updated>
		<title>Dan's Musings</title></source>

	<category term="healthcare"/>

	<category term="musings"/>

	<category term="work"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2021-07-01:/424284</id>
	<link href="https://css-tricks.com/app-platform-on-digital-ocean/" rel="alternate" type="text/html"/>
	<title type="html">App Platform on Digital Ocean</title>
	<summary type="html"><![CDATA[<p>This is new stuff from DO.



App Platform is a hosting product, no surprise there, but it has some...</p>]]></summary>
	<content type="html"><![CDATA[<p>This is <em>new</em> stuff from DO.</p>



<p><a href="https://try.digitalocean.com/css-tricks/?utm_medium=partner&amp;utm_source=cfe&amp;utm_campaign=global_brand_blog_en&amp;utm_content=conversion" rel="noopener noreferrer" target="_blank">App Platform</a> is a hosting product, no surprise there, but it has some features that are Jamstack-inspired in the best possible way, and an additional set of unique and powerful features. Let&rsquo;s start with some basics:</p>



<ul><li>Static sites can be hosted on the free tier</li><li>Automatic HTTPS </li><li>Global CDN (Cloudflare is in front, so you&rsquo;re DDoS safe)</li><li><strong>Deploy from Git</strong> </li></ul><p>That&rsquo;s the stuff that developers like me are loving these days. Take some of the hardest, toil-laden, no-fun aspects of web development and entirely do them for me. </p>



<p>And now the drumroll:</p>



<div><figure><img loading="lazy" src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/97bad042-e074-4770-a07c-f2067d5aed67_appplatform-devproductivity.png?resize=280%2C223&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/97bad042-e074-4770-a07c-f2067d5aed67_appplatform-devproductivity.png?w=575&amp;ssl=1 575w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/97bad042-e074-4770-a07c-f2067d5aed67_appplatform-devproductivity.png?resize=300%2C239&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure></div>



<ul><li>This isn&rsquo;t just for static sites: it&rsquo;s for PHP, Node, Python, Ruby, Go, Docker Containers, etc.</li><li>You don&rsquo;t have to configure and update things, these are boxes ready-to-go for those technologies.</li><li>You can scale to whatever you need.</li><li>You don&rsquo;t pay by the team seat. Unlimited team members. You pay by usage like bandwidth and build time.</li></ul><div>
<div>
<div>
<div><a href="https://try.digitalocean.com/css-tricks/?utm_medium=partner&amp;utm_source=cfe&amp;utm_campaign=global_brand_blog_en&amp;utm_content=conversion" rel="noopener noreferrer" target="_blank">Try App Platform</a></div>
</div>
</div>



<div>
<p><small>Use that link to get $100 in credit over 60 days.</small></p>
</div>
</div>



<h3>It extremely easy to deploy a static site</h3>



<p>You snag it right from GitHub (or GitLab, or Docker Hub), which is great right away, and off you go. </p>



<figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=1024%2C779&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=1024%2C779&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=300%2C228&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=768%2C584&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=1536%2C1169&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=2048%2C1558&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.28.56-AM.png?resize=1000%2C761&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?resize=1002%2C1024&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?resize=1002%2C1024&amp;ssl=1 1002w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?resize=293%2C300&amp;ssl=1 293w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?resize=768%2C785&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?resize=1502%2C1536&amp;ssl=1 1502w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?resize=1000%2C1022&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.38.55-AM.png?w=1964&amp;ssl=1 1964w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>Then we get our first little hint of something compelling right away:</p>



<figure><img loading="lazy" src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=1024%2C779&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=1024%2C779&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=300%2C228&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=768%2C584&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=1536%2C1169&amp;ssl=1 1536w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=2048%2C1558&amp;ssl=1 2048w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.29.37-AM.png?resize=1000%2C761&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>But let&rsquo;s say we don&rsquo;t need that immediately, we can go with a free plan and get this out.</p>



<figure><img loading="lazy" src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=1024%2C982&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=1024%2C982&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=300%2C288&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=768%2C737&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=1536%2C1473&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=2048%2C1965&amp;ssl=1 2048w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.31.26-AM-1.png?resize=1000%2C959&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>The site will build and you can see logs:</p>



<figure><img loading="lazy" src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?resize=1024%2C369&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?resize=1024%2C369&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?resize=300%2C108&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?resize=768%2C277&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?resize=1536%2C554&amp;ssl=1 1536w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?resize=1000%2C360&amp;ssl=1 1000w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.32.30-AM.png?w=1576&amp;ssl=1 1576w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><figure><img loading="lazy" src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=1024%2C982&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=1024%2C982&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=300%2C288&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=768%2C737&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=1536%2C1473&amp;ssl=1 1536w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=2048%2C1965&amp;ssl=1 2048w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.15-AM.png?resize=1000%2C959&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>And lookie that my static site is LIVE! </p>



<figure><img loading="lazy" src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?resize=1024%2C870&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?resize=1024%2C870&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?resize=300%2C255&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?resize=768%2C653&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?resize=1536%2C1305&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?resize=1000%2C850&amp;ssl=1 1000w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.33.31-AM.png?w=1772&amp;ssl=1 1772w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>Say my site needs to run an actual build process? That, and lots more configuration come in the form of an &ldquo;App Spec&rdquo;. This is where I would include those build commands, change Git information, deployment zones, and <a href="https://docs.digitalocean.com/products/app-platform/references/app-specification-reference/" rel="noopener noreferrer" target="_blank">loads more</a>. </p>



<figure><img loading="lazy" src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?resize=1024%2C675&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?resize=1024%2C675&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?resize=300%2C198&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?resize=768%2C506&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?resize=1536%2C1013&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?resize=1000%2C659&amp;ssl=1 1000w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.35.37-AM.png?w=1568&amp;ssl=1 1568w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><h3>About that database&hellip;</h3>



<p>Wasn&rsquo;t that interesting to see the setup steps for this static site suggest adding a database? So many sites need some kind of data store, and it&rsquo;s often left up to developers to go find some kind of cloud-accessible data storage that will work well with their app. With Digital Ocean App Platform, it can live right alongside your static app. </p>



<p><strong>It&rsquo;s called a component. </strong></p>



<figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=1024%2C847&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=1024%2C847&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=300%2C248&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=768%2C635&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=1536%2C1271&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=2048%2C1694&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.42.36-AM.png?resize=1000%2C827&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>As you can see, it can be, but doesn&rsquo;t have to be a Database. It could be another type of server! Here I could pop a PostgreSQL DB on there for just $7/month. </p>



<figure><img loading="lazy" src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=1024%2C756&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=1024%2C756&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=300%2C221&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=768%2C567&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=1536%2C1133&amp;ssl=1 1536w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=2048%2C1511&amp;ssl=1 2048w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-17-at-11.44.08-AM.png?resize=1000%2C738&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>If what you need to add is an internal or external service, it will let you add that <strong>via another Git repo</strong> that you hook up. Oh my what a modern system you now have. A front end and a back end each individually deployable directly via Git itself. </p>



<h3>This is for server-side apps as well.</h3>



<p>This feels big to me! I get that same kinda easy DX feeling I get with static sites, but with, say, a Python or Ruby on Rails app. Free deployment! Server boxes I don&rsquo;t have to configure and manage myself! </p>



<p>Seems like a pretty happy-path hosting environment for lots of stuff.</p>



<div>
<div>
<div>
<div><a href="https://try.digitalocean.com/css-tricks/?utm_medium=partner&amp;utm_source=cfe&amp;utm_campaign=global_brand_blog_en&amp;utm_content=conversion" rel="noopener noreferrer" target="_blank">Try App Platform</a></div>
</div>
</div>



<div>
<p><small>Use that link to get $100 in credit over 60 days.</small></p>
</div>
</div>
<hr><p><small>The post <a rel="noopener noreferrer" href="https://css-tricks.com/app-platform-on-digital-ocean/" target="_blank">App Platform on Digital Ocean</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>. You can support CSS-Tricks by being an <a href="https://css-tricks.com/product/mvp-supporter/" rel="noopener noreferrer" target="_blank">MVP Supporter</a>.</small></p>]]></content>
	<updated>2021-07-01T18:54:12+00:00</updated>
	<author><name>Chris Coyier</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2021-07-01T18:54:12+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>

	<category term="digital ocean"/>

	<category term="hosting"/>

	<category term="jamstack"/>

	<category term="sponsored"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2021-05-23:/422909</id>
	<link href="http://feedproxy.google.com/~r/Dudeiwantthat/~3/qjdo0vLyu0k/chopchucks-skill-toy-chopsticks.asp" rel="alternate" type="text/html"/>
	<title type="html">ChopChucks Skill Toy &amp; Chopsticks</title>
	<summary type="html"><![CDATA[]]></summary>
	<content type="html"><![CDATA[<img src="https://static.dudeiwantthat.com/img/gear/food-drink/chopchucks-skill-toy-49520.jpg" referrerpolicy="no-referrer"><img src="http://feeds.feedburner.com/~r/Dudeiwantthat/~4/qjdo0vLyu0k" alt="" referrerpolicy="no-referrer">]]></content>
	<updated>2021-05-23T18:28:00+00:00</updated>
	<author><name>info@dudeiwantthat.com Erin Carstens</name></author>
	<source>
		<id>http://www.dudeiwantthat.com</id>
		<link rel="self" href="http://www.dudeiwantthat.com"/>
		<updated>2021-05-23T18:28:00+00:00</updated>
		<title>DudeIWantThat.com</title></source>

	<category term="chopsticks"/>

	<category term="coordination"/>

	<category term="fidget toy"/>

	<category term="nunchucks"/>

	<category term="nunchuks"/>

	<category term="skill"/>

	<category term="utensil"/>


	<link rel="enclosure" 
		type="image/jpeg" 
		length="1000"
		href="https://static.dudeiwantthat.com/img/gear/food-drink/chopchucks-skill-toy-49520.jpg"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-12-14:/416360</id>
	<link href="https://css-tricks.com/give-users-control-the-media-session-api/" rel="alternate" type="text/html"/>
	<title type="html">Give Users Control: The Media Session API</title>
	<summary type="html"><![CDATA[<p>Here&rsquo;s a scenario. You start a banging Kendrick Lamar track in one of your many open browser tabs. ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Here&rsquo;s a scenario. You start a banging <a href="https://en.wikipedia.org/wiki/Kendrick_Lamar" rel="noopener noreferrer" target="_blank">Kendrick Lamar</a> track in one of your many open browser tabs. You&rsquo;re loving it, but someone walks into your space and you need to pause it. Which tab is it? Browsers try to help with that a little bit. You can probably mute the entire system audio. But wouldn&rsquo;t it be nice to actually have control over the audio playback without necessarily needing to find your way back to that tab?</p>



<p>The Media Session API makes this possible. It gives media playback access to the user <em>outside</em> of the browser tab where it is playing. If implemented, it will be available in various places on the device, including:</p>



<ul><li>the notifications area on many mobile devices,</li><li>on other wearables, and</li><li>the media hub area of many desktop devices.</li></ul><p>In addition, the Media Session API allows us to control media playback with media keys and voice assistants like Siri, Google Assistant, Bixby, or Alexa.</p>



<span></span>



<figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=1024%2C1004&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=1024%2C1004&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=300%2C294&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=768%2C753&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=1536%2C1507&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=2048%2C2009&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496534217_media-control.png?resize=1000%2C981&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><h3>The Media Session API</h3>



<p>The Media Session API mainly consists of the two following interfaces:</p>



<ul><li><code>MediaMetadata</code></li><li><code>MediaSession</code></li></ul><p>The <code>MediaMetadata</code> interface is what provides data about the playing media. It is responsible for letting us know the media&rsquo;s title, album, artwork and artist (which is Kendrick Lamar in this example). The <code>MediaSession</code> interface is what is responsible for the media playback functionality.</p>



<p>Before we take a deep dive into the topic, we would have to take note of feature detection. It is good practice to check if a browser supports a feature before implementing it. To check if a browser supports the Media Session API, we would have to include the following in our JavaScript file:</p>



<pre rel="JavaScript"><code markup="tt">if ('mediaSession' in navigator) {
  // Our media session api that lets us seek to the beginning of Kendrick Lamar's &amp;quot;Alright&amp;quot;
}</code></pre>



<h4>The MediaMetadata interface</h4>



<p>The constructor, <code>MediaMetadata.MediaMetadata()</code> creates a new <code>MediaMetadata</code> object. After creating it, we can add the following properties:</p>



<ul><li><code>MediaMetadata.title</code> sets or gets the title of the media playing.</li><li><code>MediaMetadata.artist</code> sets or gets the name of the artist or group of the media playing.</li><li><code>MediaMetadata.album</code> sets or gets the name of the album containing the media playing.</li><li><code>MediaMetadata.artwork</code> sets or gets the array of images related with the media playing.</li></ul><p>The value of the <code>artwork</code> property of the <code>MediaMetadata</code> object is an array of <code>MediaImage</code> objects. A <code>MediaImage</code> object contains details describing an image associated with the media. The objects have the three following properties:</p>



<ul><li><code>src</code>: the URL of the image</li><li><code>sizes</code>: indicates the size of the image so one image does not have to be scaled</li><li><code>type</code>: the MIME type of the image</li></ul><p>Let&rsquo;s create a <code>MediaMetadata</code> object for Kendrick Lamar&rsquo;s &ldquo;Alright&rdquo; off his <em>To Pimp a Butterfly</em> album.</p>



<pre rel="JavaScript"><code markup="tt">if ('mediaSession' in navigator) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Alright',
    artist: 'Kendrick Lamar',
    album: 'To Pimp A Butterfly',
    artwork: [
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/96x96', sizes: '96x96', type: 'image/png' },
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/128x128', sizes: '128x128', type: 'image/png' },
      // More sizes, like 192x192, 256x256, 384x384, and 512x512
    ]
  });
}</code></pre>



<h4>The MediaSession interface</h4>



<p>As stated earlier, this is what lets the user control the playback of the media. We can perform the following actions on the playing media through this interface:</p>



<ul><li><code>play</code>: play the media</li><li><code>pause</code>: pause the media</li><li><code>previoustrack</code>: switch to the previous track</li><li><code>nexttrack</code>: switch to the next track</li><li><code>seekbackward</code>: seek backward from the current position, by a few seconds</li><li><code>seekforward</code>: seek forward from the current position, by a few seconds</li><li><code>seekto</code>: seek to a specified time from the current position</li><li><code>stop</code>: stop media playback</li><li><code>skipad</code>: skip past the advertisement playing, if any</li></ul><p>The <code>MediaSessionAction</code> enumerated type makes these actions available as string types. To support any of these actions, we have to use the <code>MediaSession</code>&rsquo;s <code>setActionHandler()</code> method to define a handler for that action. The method takes the action, and a callback that is called when the user invokes the action. Let us take a not-too-deep dive to understand it better.</p>



<p>To set handlers for the <code>play</code> and <code>pause</code> actions, we include the following in our JavaScript file:</p>



<pre rel="JavaScript"><code markup="tt">let alright = new HTMLAudioElement();

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('play', () =&gt; {
    alright.play();
  });
  navigator.mediaSession.setActionHandler('pause', () =&gt; {
    alright.pause();
  });
}</code></pre>



<p>Here we set the track to <strong>play</strong> when the user plays it and <strong>pause</strong> when the user pauses it through the media interface.</p>



<p>For the <code>previoustrack</code> and <code>nexttrack</code> actions, we include the following:</p>



<pre rel="JavaScript"><code markup="tt">let u = new HTMLAudioElement();
let forSaleInterlude = new HTMLAudioElement();

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('previoustrack', () =&gt; {
    u.play();
  });
  navigator.mediaSession.setActionHandler('nexttrack', () =&gt; {
    forSaleInterlude.play();
  });
}</code></pre>



<p>This might not completely be self-explanatory if you are not much of a Kendrick Lamar fan but hopefully, you get the gist. When the user wants to play the previous track, we set the previous track to play. When it is the next track, it is the next track.</p>



<p>To implement the <code>seekbackward</code> and <code>seekforward</code> actions, we include the following:</p>



<pre rel="JavaScript"><code markup="tt">if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('seekbackward', (details) =&gt; {
    alright.currentTime = alright.currentTime - (details.seekOffset || 10);
  });
  navigator.mediaSession.setActionHandler('seekforward', (details) =&gt; {
    alright.currentTime = alright.currentTime + (details.seekOffset || 10);
  });
}</code></pre>



<p>Given that I don&rsquo;t consider any of this self-explanatory, I would like to give a concise explanation about the <code>seekbackward</code> and <code>seekforward</code> actions. The handlers for both actions, <code>seekbackward</code> and <code>seekforward</code>, are fired, as their names imply, when the user wants to seek backward or forward by a few number of seconds. The <code>MediaSessionActionDetails</code> dictionary provides us the &ldquo;few number of seconds&rdquo; in a property, <code>seekOffset</code>. However, the <code>seekOffset</code> property is not always present because not all user agents act the same way. When it is not present, we should set the track to seek backward or forward by a &ldquo;few number of seconds&rdquo; that makes sense to us. Hence, we use 10 seconds because it is quite a few. In a nutshell, we set the track to seek by <code>seekOffset</code> seconds if it is provided. If it is not provided, we seek by 10 seconds.</p>



<p>To add the <code>seekto</code> functionality to our Media Session API, we include the following snippet:</p>



<pre rel="JavaScript"><code markup="tt">if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('seekto', (details) =&gt; {
    if (details.fastSeek &amp;&amp; 'fastSeek' in alright) {
      alright.fastSeek(details.seekTime);
      return;
    }
    alright.currentTime = details.seekTime;
  });
}</code></pre>



<p>Here, the <code>MediaSessionActionDetails</code> dictionary provides the <code>fastSeek</code> and <code>seekTime</code> properties. <code>fastSeek</code> is basically seek performed rapidly (like fast-forwarding or rewinding) while <code>seekTime</code> is the time the track should seek to. While <code>fastSeek</code> is an optional property, the <code>MediaSessionActionDetails</code> dictionary always provides the <code>seekTime</code> property for the <code>seekto</code> action handler. So fundamentally, we set the track to <code>fastSeek</code> to the <code>seekTime</code> when the property is available and the user fast seeks, while we just set it to the <code>seekTime</code> when the user just seeks to a specified time.</p>



<p>Although I wouldn&rsquo;t know why one would want to stop a Kendrick song, it won&rsquo;t hurt to describe the <code>stop</code> action handler of the <code>MediaSession</code> interface:</p>



<pre rel="JavaScript"><code markup="tt">if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('stop', () =&gt; {
    alright.pause();
    alright.currentTime = 0;
  });
} </code></pre>



<p>The user invokes the <code>skipad</code> (as in, &ldquo;skip ad&rdquo; rather than &ldquo;ski pad&rdquo;) action handler when an advertisement is playing and they want to skip it so they can continue listening to Kendrick Lamar&rsquo;s <a href="https://genius.com/Kendrick-lamar-alright-lyrics#note-5047528" rel="noopener noreferrer" target="_blank">&ldquo;A</a><a href="https://genius.com/Kendrick-lamar-alright-lyrics#note-5047528" rel="noopener noreferrer" target="_blank">lright</a><a href="https://genius.com/Kendrick-lamar-alright-lyrics#note-5047528" rel="noopener noreferrer" target="_blank">&rdquo;</a> track. If I&rsquo;m being honest, the complete details of the <code>skipad</code> action handler is out of the scope of my &ldquo;Media Session API&rdquo; understanding. Hence, you should probably look that up on your own after reading this article, if you actually want to implement it.</p>



<h3>Wrapping up</h3>



<p>We should take note of something. Whenever the user plays the track, seeks, or changes the playback rate, we are supposed to update the position state on the interface provided by the Media Session API. What we use to implement this is the <code>setPositionState()</code> method of the <code>mediaSession</code> object, as in the following:</p>



<pre rel="JavaScript"><code markup="tt">if ('mediaSession' in navigator) {
  navigator.mediaSession.setPositionState({
    duration: alright.duration,
    playbackRate: alright.playbackRate,
    position: alright.currentTime
  });
}</code></pre>



<p>In addition, I would like to remind you that not all browsers of the users would support all the actions. Therefore, it is recommended to set the action handlers in a <code>try...catch</code> block, as in the following:</p>



<pre rel="JavaScript"><code markup="tt">const actionsAndHandlers = [
  ['play', () =&gt; { /*...*/ }],
  ['pause', () =&gt; { /*...*/ }],
  ['previoustrack', () =&gt; { /*...*/ }],
  ['nexttrack', () =&gt; { /*...*/ }],
  ['seekbackward', (details) =&gt; { /*...*/ }],
  ['seekforward', (details) =&gt; { /*...*/ }],
  ['seekto', (details) =&gt; { /*...*/ }],
  ['stop', () =&gt; { /*...*/ }]
]
 
for (const [action, handler] of actionsAndHandlers) {
  try {
    navigator.mediaSession.setActionHandler(action, handler);
  } catch (error) {
    console.log(`The media session action, ${action}, is not supported`);
  }
}</code></pre>



<p>Putting everything we have done, we would have the following:</p>



<pre rel="JavaScript"><code markup="tt">let alright = new HTMLAudioElement();
let u = new HTMLAudioElement();
let forSaleInterlude = new HTMLAudioElement();

const updatePositionState = () =&gt; {
  navigator.mediaSession.setPositionState({
    duration: alright.duration,
    playbackRate: alright.playbackRate,
    position: alright.currentTime
  });
}
 
const actionsAndHandlers = [
  ['play', () =&gt; {
    alright.play();
    updatePositionState();
  }],
  ['pause', () =&gt; { alright.pause(); }],
  ['previoustrack', () =&gt; { u.play(); }],
  ['nexttrack', () =&gt; { forSaleInterlude.play(); }],
  ['seekbackward', (details) =&gt; {
    alright.currentTime = alright.currentTime - (details.seekOffset || 10);
    updatePositionState();
  }],
  ['seekforward', (details) =&gt; {
    alright.currentTime = alright.currentTime + (details.seekOffset || 10);
    updatePositionState();
  }],
  ['seekto', (details) =&gt; {
    if (details.fastSeek &amp;&amp; 'fastSeek' in alright) {
      alright.fastSeek(details.seekTime);
      updatePositionState();
      return;
    }
    alright.currentTime = details.seekTime;
    updatePositionState();
  }],
  ['stop', () =&gt; {
    alright.pause();
    alright.currentTime = 0;
  }],
]
 
if ( 'mediaSession' in navigator ) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Alright',
    artist: 'Kendrick Lamar',
    album: 'To Pimp A Butterfly',
    artwork: [
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/96x96', sizes: '96x96', type: 'image/png' },
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/128x128', sizes: '128x128', type: 'image/png' },
      // More sizes, like 192x192, 256x256, 384x384, and 512x512
    ]
  });
 
  for (const [action, handler] of actionsAndHandlers) {
    try {
      navigator.mediaSession.setActionHandler(action, handler);
    } catch (error) {
      console.log(`The media session action, ${action}, is not supported`);
    }
  }
}</code></pre>



<p>Here&rsquo;s a demo of the API:</p>



<div></div>



<p>I implemented six of the actions. Feel free to try the rest during your leisure.</p>



<p>If you view the Pen on your mobile device, notice how it appears on your notification area.</p>



<div>
<div>
<figure><img loading="lazy" src="https://paper-attachments.dropbox.com/s_B4FDC13572C3BFB831628BBAFC832AFD1E5082A4E169106B418D5F09A64E026F_1606496915007_ios-media-control.png" alt="" referrerpolicy="no-referrer"></figure></div>



<div>
<p>If your smart watch is paired to your device, take a sneak peek at it.</p>



<p>If you <a href="https://codepen.io/idorenyinudoh/pen/vYKQVqQ" rel="noopener noreferrer" target="_blank">view the</a> <a href="https://codepen.io/idorenyinudoh/pen/vYKQVqQ" rel="noopener noreferrer" target="_blank">P</a><a href="https://codepen.io/idorenyinudoh/pen/vYKQVqQ" rel="noopener noreferrer" target="_blank">en</a> on Chrome on desktop, navigate to the media hub and play with the media buttons there. The demo even has multiple tracks, so you experiment moving forward/back through tracks.</p>



<p>If you made it this far (or not), thanks for reading and please, on the next app you create with media functionality, implement this API.</p>
</div>
</div>
<hr><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/give-users-control-the-media-session-api/" target="_blank">Give Users Control: The Media Session API</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>
<p>You can support CSS-Tricks by being an <a href="https://css-tricks.com/product/mvp-supporter/" rel="noopener noreferrer" target="_blank">MVP Supporter</a>.</p>]]></content>
	<updated>2020-12-14T15:47:47+00:00</updated>
	<author><name>Idorenyin Udoh</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-12-14T15:47:47+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-12-11:/416289</id>
	<link href="https://kottke.org/20/12/identical-strangers" rel="alternate" type="text/html"/>
	<title type="html">Identical Strangers</title>
	<summary type="html"><![CDATA[<p>With almost 8 billion people in the world, chances are pretty good that everyone has a do...</p>]]></summary>
	<content type="html"><![CDATA[<p></p>

<p>With almost 8 billion people in the world, chances are pretty good that everyone has a doppelg&auml;nger somewhere out there. Finding My Twin Stranger is a short documentary that follows researchers from <a href="https://www.guysandstthomas.nhs.uk/research/studies/twins.aspx" rel="noopener noreferrer" target="_blank">the Department of Twin Research at St Thomas&rsquo; Hospital</a> as they look for the most identical strangers &mdash; people who aren&rsquo;t biological twins but sure look like they do.</p>

<blockquote><p>In this documentary, we follow people as they track down their twin stranger across the globe and meet for the first time. They get to know one another and find out about each other&rsquo;s lives and whether there are any other similarities. While the pairs get to know each other, they are also undergoing a series of tests by the twin-experts at St Thomas&rsquo; Hospital. These include measuring similarities of facial features using the latest 3D scanning technology, 2D facial recognition analysis and DNA ancestry testing.</p>

<p>The experts then get 100 people to rate the similarities from photos from most alike to least alike. Taking all of the results into consideration, the twin-experts will then reveal which pair is the most identical.</p></blockquote>

<p>You can use the site <a href="https://twinstrangers.net/" rel="noopener noreferrer" target="_blank">Twin Strangers</a> to find your own doppelg&auml;nger &mdash; you can see some of their matches <a href="https://twinstrangers.net/twin-strangers-exist" rel="noopener noreferrer" target="_blank">here</a> and <a href="https://www.instagram.com/twinstrangers" rel="noopener noreferrer" target="_blank">on Instagram</a>.</p>

<p>See also the fascinating 2018 documentary <a href="https://kottke.org/18/04/three-identical-strangers" rel="noopener noreferrer" target="_blank">Three Identical Strangers</a> and <a href="https://kottke.org/19/05/identical-twins-who-look-nothing-alike" rel="noopener noreferrer" target="_blank">Identical Twins Who Look Nothing Alike</a>.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/video" rel="noopener noreferrer" target="_blank">video</a>]]></content>
	<updated>2020-12-11T15:50:00+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-12-11T15:50:00+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-12-11:/416274</id>
	<link href="https://css-tricks.com/unconventional-stock-image-sources/" rel="alternate" type="text/html"/>
	<title type="html">Unconventional Stock Image Sources</title>
	<summary type="html"><![CDATA[<p>This year, I learned that there is a wide world of free stock imagery available beyond Unsplash and...</p>]]></summary>
	<content type="html"><![CDATA[<p>This year, I learned that there is a wide world of free stock imagery available beyond Unsplash and Pexels. You see, I&rsquo;ve been working on designing WordPress themes this year, and all images need to be compatible with the GPL. Unsplash and Pexels both have free and open licenses, but unfortunately, aren&rsquo;t compatible. Many other free stock photos sites don&rsquo;t have the highest quality photos, so I&rsquo;ve had to get creative about where I get the imagery I use in my mockups.</p>



<span></span>



<p>I discovered the solution to my stock imagery problem, ironically, on Unsplash. I started noticing photos from sources like the <a rel="noopener noreferrer" target="_blank" href="https://unsplash.com/@britishlibrary">British Library</a>, <a rel="noopener noreferrer" target="_blank" href="https://unsplash.com/@birminghammuseumstrust">Birmingham Museums Trust</a>, and <a rel="noopener noreferrer" target="_blank" href="https://unsplash.com/@libraryofcongress/">Library of Congress</a>. Who often has archives of public domain imagery? Libraries, museums, and governments. The sites are never a site like Unsplash, but they work well if you have the time and patience to dive through their archives. Plus? You can find some pretty cool photography, art, and illustrations that have a very different vibe than most stock photo resources.</p>



<h2>Libraries</h2>



<figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=2880%2C2292&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?w=2880&amp;ssl=1 2880w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=300%2C239&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=1024%2C815&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=768%2C611&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=1536%2C1222&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=2048%2C1630&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1606953532763_image.png?resize=1000%2C796&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>There are tons of libraries with license-compatible digital archives, such as the <a target="_blank" href="https://www.nypl.org/research/collections/digital-collections/public-domain" rel="noopener noreferrer">New York Public Library</a>, <a target="_blank" href="https://www.loc.gov/free-to-use/" rel="noopener noreferrer">Library of Congress</a>, <a target="_blank" href="https://www.flickr.com/people/statelibraryofnsw/" rel="noopener noreferrer">The State Library of New South Wales</a>, <a target="_blank" href="https://www.flickr.com/people/nlireland/" rel="noopener noreferrer">National Library of Ireland</a>, and many more. Try seeing if a major city with your state has a digital archive. Libraries are great for old photos, advertisements, and illustrations that I&rsquo;ll use in portfolio site designs.</p>



<h2>Museums</h2>



<figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=2880%2C2400&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?w=2880&amp;ssl=1 2880w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=300%2C250&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=1024%2C853&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=768%2C640&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=1536%2C1280&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=2048%2C1707&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013567282_image.png?resize=1000%2C833&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>Many museums have started digitizing their collections in the past few years, such as the Smithsonian&rsquo;s <a target="_blank" href="https://collections.si.edu/search/results.htm?q=&amp;media.CC0=true&amp;fq=data_source%3A%22Freer+Gallery+of+Art+and+Arthur+M.+Sackler+Gallery%22" rel="noopener noreferrer">National Museum of Asian Art</a>, the <a target="_blank" href="https://www.metmuseum.org/art/collection/search#!?showOnly=openAccess&amp;offset=0&amp;pageSize=0&amp;perPage=20&amp;searchField=All&amp;sortBy=Relevance" rel="noopener noreferrer">Met</a>, and the <a target="_blank" href="https://www.artic.edu/collection?is_public_domain=1" rel="noopener noreferrer">Art Institute of Chicago</a>. As the museums are often digitizing the work themselves, they have the luxury of releasing the images into the public domain. Museums are a fantastic resource for art, and for photos of objects like ceramics and jewelry that work well in e-commerce mockups.</p>



<h2>Governments</h2>



<figure><img loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=2880%2C2160&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?w=2880&amp;ssl=1 2880w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=300%2C225&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=2048%2C1536&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/12/s_C735DDE985D5A887CD66F6D69F4A951C5172B58883B3C4F0B131DB8B927FB5D4_1607013424494_image.png?resize=1000%2C750&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>US Government agencies like <a rel="noopener noreferrer" target="_blank" href="https://images.nasa.gov/">NASA</a> tend to release a ton of their own media for public use, and I&rsquo;ve discovered that space images look great in blog post mockups. Need some COVID photos? <a rel="noopener noreferrer" target="_blank" href="https://phil.cdc.gov/default.aspx">The CDC&rsquo;s got you covered</a>. Need some black &amp; white nature photos? Check out the National Park Service&rsquo;s &ldquo;<a href="https://openparksnetwork.org/" rel="noopener noreferrer" target="_blank">Open Parks Network.</a>&rdquo;&nbsp;</p>



<hr><p>Finding high-quality, totally free stock imagery can be a huge hassle. But I&rsquo;ve found, with some creativity and some patience, there are far more options than I knew!</p>
<hr><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/unconventional-stock-image-sources/" target="_blank">Unconventional Stock Image Sources</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>
<p>You can support CSS-Tricks by being an <a href="https://css-tricks.com/product/mvp-supporter/" rel="noopener noreferrer" target="_blank">MVP Supporter</a>.</p>]]></content>
	<updated>2020-12-10T22:42:21+00:00</updated>
	<author><name>Mel Choyce</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-12-10T22:42:21+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="2020 end-of-year thoughts"/>

	<category term="article"/>

	<category term="stock images"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-11-11:/414962</id>
	<link href="https://kottke.org/20/11/no-evidence-of-voter-fraud-reported-by-election-officials-nationwide" rel="alternate" type="text/html"/>
	<title type="html">No Evidence of Voter Fraud Reported by Election Officials Nationwide</title>
	<summary type="html"><![CDATA[<p>The headline and first few paragraphs of today&rsquo;s front-page story in the NY Times say...</p>]]></summary>
	<content type="html"><![CDATA[<p>The headline and first few paragraphs of <a href="https://twitter.com/nytimes/status/1326511281914994689" rel="noopener noreferrer" target="_blank">today&rsquo;s front-page story</a> in the NY Times say it all: <a href="https://www.nytimes.com/2020/11/10/us/politics/voting-fraud.html" rel="noopener noreferrer" target="_blank">The Times Called Officials in Every State: No Evidence of Voter Fraud</a>.</p>

<blockquote><p>Election officials in dozens of states representing both political parties said that there was no evidence that fraud or other irregularities played a role in the outcome of the presidential race, amounting to a forceful rebuke of President Trump&rsquo;s portrait of a fraudulent election.</p>

<p>Over the last several days, the president, members of his administration, congressional Republicans and right wing allies have put forth the false claim that the election was stolen from Mr. Trump and have refused to accept results that showed Joseph R. Biden Jr. as the winner.</p>

<p>But top election officials across the country said in interviews and statements that the process had been a remarkable success despite record turnout and the complications of a dangerous pandemic.</p></blockquote>

<p>Of course, the Republicans have made no fraud claims about states where they did well. Every Republican who the media projected to be the winner has claimed victory on that basis. And if the Democrats were somehow cheating, wouldn&rsquo;t they have done far better on down-ballot races &mdash; presumably hundreds of thousands of fake ballots for Biden would also have gone for Democratic Senate and House candidates as well? The whole thing is obviously absurd.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/2020%20election" rel="noopener noreferrer" target="_blank">2020 election</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Donald%20Trump" rel="noopener noreferrer" target="_blank">Donald Trump</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Joe%20Biden" rel="noopener noreferrer" target="_blank">Joe Biden</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/NY%20Times" rel="noopener noreferrer" target="_blank">NY Times</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/politics" rel="noopener noreferrer" target="_blank">politics</a>]]></content>
	<updated>2020-11-11T14:35:19+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-11-11T14:35:19+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-10-29:/414119</id>
	<link href="https://kottke.org/20/10/karen-o-and-willie-nelson-cover-under-pressure" rel="alternate" type="text/html"/>
	<title type="html">Karen O and Willie Nelson Cover Under Pressure</title>
	<summary type="html"><![CDATA[<p>Under Pressure, the classic tune from David Bowie and Queen, seems like one of those songs ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Under Pressure, the classic tune from David Bowie and Queen, seems like one of those songs you don&rsquo;t want to mess with &mdash; we&rsquo;re looking at you here, Vanilla Ice. But if someone is going to cover it, it might as well be Karen O from the Yeah Yeah Yeahs and Willie Nelson.</p>

<p></p>

<p>(via <a href="https://www.openculture.com/2020/10/karen-o-willie-nelson-cover-bowie-queens-under-pressure.html" rel="noopener noreferrer" target="_blank">open culture</a>)</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/David%20Bowie" rel="noopener noreferrer" target="_blank">David Bowie</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Karen%20O" rel="noopener noreferrer" target="_blank">Karen O</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/music" rel="noopener noreferrer" target="_blank">music</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Queen" rel="noopener noreferrer" target="_blank">Queen</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/remix" rel="noopener noreferrer" target="_blank">remix</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/video" rel="noopener noreferrer" target="_blank">video</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Willie%20Nelson" rel="noopener noreferrer" target="_blank">Willie Nelson</a>]]></content>
	<updated>2020-10-29T15:24:46+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-10-29T15:24:46+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-09-14:/411255</id>
	<link href="https://kottke.org/20/09/its-a-bird" rel="alternate" type="text/html"/>
	<title type="html">It’s a Bird</title>
	<summary type="html"><![CDATA[<p>In late May, Christian Cooper was birdwatching in Central Park when he was accosted and abu...</p>]]></summary>
	<content type="html"><![CDATA[<p>In late May, Christian Cooper was birdwatching in Central Park when he was <a href="https://www.nytimes.com/2020/05/26/nyregion/amy-cooper-dog-central-park.html" rel="noopener noreferrer" target="_blank">accosted and abused for the color of his skin by a white woman</a> after he asked her to leash her dog. Cooper, who is both an avid birdwatcher (he&rsquo;s on the board of directors for <a href="http://www.nycaudubon.org/" rel="noopener noreferrer" target="_blank">the NYC Audubon Society</a>) and a pioneering comics writer (he was <a href="https://www.syfy.com/syfywire/christian-cooper-looks-back-on-lgbtq-comic-storytelling-for-marvel" rel="noopener noreferrer" target="_blank">Marvel&rsquo;s first openly gay writer and editor</a>), has combined his experiences and interests into a new graphic novel for DC Comics called <a href="https://www.readdc.com/Represent-2020-1/digital-comic/T2139800015001" rel="noopener noreferrer" target="_blank">It&rsquo;s a Bird</a>.</p>

<p><img src="https://tt-rss.goodevilgenius.org/plus/misc/images/its-a-bird-cooper.jpg" border="0" alt="It's A Bird, Christian Cooper" referrerpolicy="no-referrer"></p>

<p>From <a href="https://www.nytimes.com/2020/09/09/nyregion/christian-cooper-amy-comic-graphic-novel.html" rel="noopener noreferrer" target="_blank">the NY Times</a>:</p>

<blockquote><p>The slim, 10-page story is impressionistic, without a real plot. It is the first in a series called &ldquo;Represent!&rdquo; that features works of writers &ldquo;traditionally underrepresented in the mainstream comic book medium,&rdquo; including people of color or those who are LGBTQ, Marie Javins, an executive editor at DC, said in a statement. It will be available online for free starting Wednesday, at several digital book and comic book retailers.</p>

<p>The main character of &ldquo;It&rsquo;s a Bird&rdquo; is a teenage birder named Jules, who is Black. When Jules tries to peer through his binoculars at birds, he instead sees the faces of Black people who have been killed by the police.</p></blockquote>

<p>It&rsquo;s a Bird is available for <a href="https://www.readdc.com/Represent-2020-1/digital-comic/T2139800015001" rel="noopener noreferrer" target="_blank">free from DC Comics</a>. You can read an interview with Cooper and the rest of the creative team (artist Alitha E. Martinez, inker Mark Morales, colorist Emilio Lopez, and letterer Rob Clark Jr.) <a href="https://www.dccomics.com/blog/2020/09/09/represent-creative-team-talks-real-life-inspiration-and-stories-of-hope" rel="noopener noreferrer" target="_blank">on the company&rsquo;s blog</a>. (via <a href="http://www.openculture.com/2020/09/central-park-bird-watcher-christian-cooper-writes-dc-comics-graphic-novel-its-now-free-online.html" rel="noopener noreferrer" target="_blank">open culture</a>)</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/birds" rel="noopener noreferrer" target="_blank">birds</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Christian%20Cooper" rel="noopener noreferrer" target="_blank">Christian Cooper</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/comics" rel="noopener noreferrer" target="_blank">comics</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/racism" rel="noopener noreferrer" target="_blank">racism</a>]]></content>
	<updated>2020-09-14T15:00:58+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-09-14T15:00:58+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-08-20:/409816</id>
	<link href="https://kottke.org/20/08/ukulele-covers-of-acdc-nirvana-and-guns-n-roses-hits" rel="alternate" type="text/html"/>
	<title type="html">Ukulele Covers of AC/DC, Nirvana, and Guns N’ Roses Hits</title>
	<summary type="html"><![CDATA[<p>I have a bit of a thing for kooky covers of AC/DC&rsquo;s Thunderstruck &mdash; see Thunder...</p>]]></summary>
	<content type="html"><![CDATA[<p>I have a bit of a thing for kooky covers of AC/DC&rsquo;s Thunderstruck &mdash; see <a href="https://kottke.org/10/11/acdcs-thunderstruck-on-the-bagpipes" rel="noopener noreferrer" target="_blank">Thunderstruck on the bagpipes</a> and <a href="https://kottke.org/17/02/the-devil-went-down-to-georgia-washing-machine-edition" rel="noopener noreferrer" target="_blank">on a washing machine</a> &mdash; so I was plum tickled to find this ukulele cover today:</p>

<p></p>

<p>That&rsquo;s from a Brazilian duo called Overdriver Duo, who have also done <a href="https://www.youtube.com/watch?v=hqTBxTqbInk" rel="noopener noreferrer" target="_blank">GNR&rsquo;s Sweet Child O&rsquo; Mine</a>, <a href="https://www.youtube.com/watch?v=uMfin3FwoYc" rel="noopener noreferrer" target="_blank">Nirvana&rsquo;s Smells Like Teen Spirit</a> (on a Frozen-branded uke!), and <a href="https://www.youtube.com/watch?v=H18oBVpItzw" rel="noopener noreferrer" target="_blank">Every Breath You Take by The Police</a>. (via <a href="http://www.openculture.com/2020/08/seriously-awesome-ukulele-covers-of-sultans-of-swing.html" rel="noopener noreferrer" target="_blank">open culture</a>)</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/ACDC" rel="noopener noreferrer" target="_blank">ACDC</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/music" rel="noopener noreferrer" target="_blank">music</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Overdriver%20Duo" rel="noopener noreferrer" target="_blank">Overdriver Duo</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/remix" rel="noopener noreferrer" target="_blank">remix</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/video" rel="noopener noreferrer" target="_blank">video</a>]]></content>
	<updated>2020-08-20T17:09:49+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-08-20T17:09:49+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-08-06:/408949</id>
	<link href="https://css-tricks.com/typescript-minus-typescript/" rel="alternate" type="text/html"/>
	<title type="html">TypeScript, Minus TypeScript</title>
	<summary type="html"><![CDATA[<p>Unless you&rsquo;ve been hiding under a rock the last several years (and let&rsquo;s face it, hiding under a ro...</p>]]></summary>
	<content type="html"><![CDATA[<p>Unless you&rsquo;ve been hiding under a rock the last several years (and let&rsquo;s face it, hiding under a rock sometimes feels like the right thing to do), you&rsquo;ve probably heard of and likely used <a href="https://www.typescriptlang.org/" rel="noopener noreferrer" target="_blank">TypeScript</a>. TypeScript is a syntactical superset of JavaScript that adds &mdash; as its name suggests &mdash; typing to the web&rsquo;s favorite scripting language.</p>



<p>TypeScript is incredibly powerful, but is often difficult to read for beginners and carries the overhead of needing a compilation step before it can run in a browser due to the extra syntax that isn&rsquo;t valid JavaScript. For many projects this isn&rsquo;t a problem, but for others this might get in the way of getting work done. Fortunately the TypeScript team has enabled a way to type check vanilla JavaScript using <a href="https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler" rel="noopener noreferrer" target="_blank">JSDoc</a>.</p>



<h2>Setting up a new project</h2>



<p>To get TypeScript up and running in a new project, you&rsquo;ll need <a href="https://www.npmjs.com/get-npm" rel="noopener noreferrer" target="_blank">NodeJS and npm</a>. Let&rsquo;s start by creating a new project and running npm init. For the purposes of this article, we are going to be using <a href="https://code.visualstudio.com" rel="noopener noreferrer" target="_blank">VShttps://code.visualstudio.comCode</a> as our code editor. Once everything is set up, we&rsquo;ll need to install TypeScript:</p>



<pre rel="Terminal"><code markup="tt">npm i -D typescript</code></pre>



<p>Once that install is done, we need to tell TypeScript what to do with our code, so let&rsquo;s create a new file called <code>tsconfig.json</code> and add this:</p>



<pre rel="JavaScript"><code markup="tt">{
&nbsp; "compilerOptions": {
&nbsp; &nbsp; "target": "esnext",
&nbsp; &nbsp; "module": "esnext",
&nbsp; &nbsp; "moduleResolution": "node",
&nbsp; &nbsp; "lib": ["es2017", "dom"],
&nbsp; &nbsp; "allowJs": true,
&nbsp; &nbsp; "checkJs": true,
&nbsp; &nbsp; "noEmit": true,
&nbsp; &nbsp; "strict": false,
&nbsp; &nbsp; "noImplicitThis": true,
&nbsp; &nbsp; "alwaysStrict": true,
&nbsp; &nbsp; "esModuleInterop": true
&nbsp; },
&nbsp; "include": [ "script", "test" ],
&nbsp; "exclude": [ "node_modules" ]
}</code></pre>



<p>For our purposes, the important lines of this config file are the <code>allowJs</code> and <code>checkJs</code> options, which are both set to <code>true</code>. These tell TypeScript that we want it to evaluate our JavaScript code. We&rsquo;ve also told TypeScript to check all files inside of a <code>/script</code> directory, so let&rsquo;s create that and a new file in it called <code>index.js</code>.</p>



<h3>A simple example</h3>



<p>Inside our newly-created JavaScript file, let&rsquo;s make a simple addition function that takes two parameters and adds them together:</p>



<pre rel="JavaScript"><code markup="tt">function add(x, y) {
&nbsp; return x + y;
}</code></pre>



<p>Fairly simple, right? <code>add(4, 2)</code> will return <code>6</code>, but because JavaScript is <a href="https://developer.mozilla.org/en-US/docs/Glossary/Dynamic_typing" rel="noopener noreferrer" target="_blank">dynamically-typed</a> you could also call add with a string and a number and get some potentially unexpected results:</p>



<pre rel="JavaScript"><code markup="tt">add('4', 2); // returns '42'</code></pre>



<p>That&rsquo;s less than ideal. Fortunately, we can add some JSDoc annotations to our function to tell users how we expect it to work:</p>



<pre rel="JavaScript"><code markup="tt">/**
&nbsp;* Add two numbers together
&nbsp;* @param {number} x
&nbsp;* @param {number} y
&nbsp;* @return {number}
&nbsp;*/
function add(x, y) {
&nbsp; return x + y;
}</code></pre>



<p>We&rsquo;ve changed nothing about our code; we&rsquo;ve simply added a comment to tell users how the function is meant to be used and what value should be expected to return. We&rsquo;ve done this by utilizing JSDoc&rsquo;s <code>@param</code> and <code>@return</code> annotations with types set in curly braces (<code>{}</code>).</p>



<p>Trying to run our incorrect snippet from before throws an error in VS Code:</p>



<figure><img src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-25.png?resize=1024%2C445&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-25.png?resize=1024%2C445&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-25.png?resize=300%2C130&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-25.png?resize=768%2C334&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-25.png?resize=1000%2C434&amp;ssl=1 1000w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-25.png?w=1220&amp;ssl=1 1220w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>TypeScript evaluates that a call to&nbsp;<code>add</code>&nbsp;is incorrect if one of the arguments is a string.</figcaption></figure><p>In the example above, TypeScript is reading our comment and checking it for us. In actual TypeScript, our function now is equivalent of writing:</p>



<pre rel="JavaScript"><code markup="tt">/**
&nbsp;* Add two numbers together
&nbsp;*/
function add(x: number, y: number): number {
&nbsp; return x + y;
}</code></pre>



<p>Just like we used the <code>number</code> type, we have access to dozens of built-in types with JSDoc, including string, object, Array as well as plenty of others, like <code>HTMLElement</code>, <code>MutationRecord</code> and more.</p>



<p>One added benefit of using JSDoc annotations over TypeScript&rsquo;s proprietary syntax is that it provides developers an opportunity to provide additional metadata around arguments or type definitions by providing those inline (hopefully encouraging positive habits of self-documenting our code).</p>



<p>We can also tell TypeScript that instances of certain objects might have expectations. A <code>WeakMap</code>, for instance, is a built-in JavaScript object that creates a mapping between any object and any other piece of data. This second piece of data can be anything by default, but if we want our <code>WeakMap</code> instance to only take a string as the value, we can tell TypeScript what we want:</p>



<pre rel="JavaScript"><code markup="tt">/** @type {WeakMap&lt;object&gt;, string} */
const metadata = new WeakMap();
&#8232;
const object = {};
const otherObject = {};
&#8232;
metadata.set(object, 42);
metadata.set(otherObject, 'Hello world');</code></pre>



<p>This throws an error when we try to set our data to <code>42</code> because it is not a string.</p>



<figure><img src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-26.png?resize=1024%2C236&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-26.png?resize=1024%2C236&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-26.png?resize=300%2C69&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-26.png?resize=768%2C177&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-26.png?resize=1000%2C231&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-26.png?w=1464&amp;ssl=1 1464w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><h3><strong>Defining our own types</strong></h3>



<p>Just like TypeScript, JSDoc allows us to define &nbsp;and work with our own types. Let&rsquo;s create a new type called <code>Person</code> that has <code>name</code>, <code>age</code> and <code>hobby</code> properties. Here&rsquo;s how that looks in TypeScript:</p>



<pre rel="JavaScript"><code markup="tt">interface Person {
&nbsp; name: string;
&nbsp; age: number;
&nbsp; hobby?: string;
}</code></pre>



<p>In JSDoc, our type would be the following:</p>



<pre rel="JavaScript"><code markup="tt">/**
&nbsp;* @typedef Person
&nbsp;* @property {string} name - The person's name
&nbsp;* @property {number} age - The person's age
&nbsp;* @property {string} [hobby] - An optional hobby
&nbsp;*/</code></pre>



<p>We can use the <code>@typedef</code> tag to define our type&rsquo;s <code>name</code>. Let&rsquo;s define an interface called <code>Person</code> with required &nbsp;<code>name</code> (a string)) and <code>age</code> (a number) properties, plus a third, optional property called <code>hobby</code> (a string). To define these properties, we use <code>@property</code> (or the shorthand <code>@prop</code> key) inside our comment.</p>



<p>When we choose to apply the <code>Person</code> type to a new object using the <code>@type</code> comment, we get type checking and autocomplete when writing our code. Not only that, we&rsquo;ll also be told when our object doesn&rsquo;t adhere to the contract we&rsquo;ve defined in our file:</p>



<figure><img src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-27.png?resize=1024%2C334&amp;ssl=1" alt="Screenshot of an example of TypeScript throwing an error on our vanilla JavaScript object" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-27.png?resize=1024%2C334&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-27.png?resize=300%2C98&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-27.png?resize=768%2C250&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-27.png?resize=1000%2C326&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-27.png?w=1252&amp;ssl=1 1252w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>Now, completing the object will clear the error:</p>



<figure><img src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-28.png?resize=1024%2C445&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-28.png?resize=1024%2C445&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-28.png?resize=300%2C130&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-28.png?resize=768%2C334&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-28.png?resize=1000%2C435&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-28.png?w=1260&amp;ssl=1 1260w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Our object now adheres to the&nbsp;<code>Person</code>&nbsp;interface defined above</figcaption></figure><p>Sometimes, however, we don&rsquo;t want a full-fledged object for a type. For example, we might want to provide a limited set of possible options. In this case, we can take advantage of something called a <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html%23union-types" rel="noopener noreferrer" target="_blank">union type</a>:</p>



<pre rel="JavaScript"><code markup="tt">/**
&nbsp;* @typedef {'cat'|'dog'|'fish'} Pet
&nbsp;*/
&#8232;
/**
&nbsp;* @typedef Person
&nbsp;* @property {string} name - The person's name
&nbsp;* @property {number} age - The person's age
&nbsp;* @property {string} [hobby] - An optional hobby
&nbsp;* @property {Pet} [pet] - The person's pet
&nbsp;*/</code></pre>



<p>In this example, we have defined a union type called <code>Pet</code> that can be any of the possible options of <code>'cat'</code>, <code>'dog'</code> or <code>'fish'</code>. Any other animals in our area are not allowed as pets, so if <code>caleb</code> above tried to adopt a <code>'kangaroo'</code> into his household, we would get an error:</p>



<pre rel="JavaScript"><code markup="tt">/** @type {Person} */
const caleb = {
  name: 'Caleb Williams',
  age: 33,
  hobby: 'Running',
  pet: 'kangaroo'
};</code></pre>



<figure><img src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-29.png?resize=1024%2C693&amp;ssl=1" alt="Screenshot of an an example illustrating that kangaroo is not an allowed pet type" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-29.png?resize=1024%2C693&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-29.png?resize=300%2C203&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-29.png?resize=768%2C520&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-29.png?resize=1000%2C677&amp;ssl=1 1000w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-29.png?w=1250&amp;ssl=1 1250w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>This same technique can be utilized to mix various types in a function:</p>



<pre rel="JavaScript"><code markup="tt">/**
&nbsp;* @typedef {'lizard'|'bird'|'spider'} ExoticPet
&nbsp;*/
&#8232;
/**
&nbsp;* @typedef Person
&nbsp;* @property {string} name - The person's name
&nbsp;* @property {number} age - The person's age
&nbsp;* @property {string} [hobby] - An optional hobby
&nbsp;* @property {Pet|ExoticPet} [pet] - The person's pet
&nbsp;*/</code></pre>



<p>Now our person type can have either a <code>Pet</code> or an <code>ExoticPet</code>.</p>



<h3>Working with generics</h3>



<p>There could be times when we don&rsquo;t want hard and fast types, but a little more flexibility while still writing consistent, strongly-typed code. Enter <a href="https://www.typescriptlang.org/docs/handbook/generics.html" rel="noopener noreferrer" target="_blank">generic types</a>. The classic example of a generic function is the identity function, which takes an argument and returns it back to the user. In TypeScript, that looks like this:</p>



<pre rel="JavaScript"><code markup="tt">function identity&lt;T&gt;(target: T): T {
&nbsp; return target;
}</code></pre>



<p>Here, we are defining a new generic type (<code>T</code>) and telling the computer and our users that the function will return a value that shares a type with whatever the argument <code>target</code> is. This way, we can still pass in a number or a string or an <code>HTMLElement</code> and have the assurance that the returned value is also of that same type.</p>



<p>The same thing is possible using the JSDoc notation using the <code>@template</code> annotation:</p>



<pre rel="JavaScript"><code markup="tt">/**
&nbsp;* @template T
&nbsp;* @param {T} target
&nbsp;* @return {T}
&nbsp;*/
function identity(target) {
&nbsp; return x;
}</code></pre>



<p>Generics are a complex topic, but for more detailed documentation on how to utilize them in JSDoc, including examples, you can read the <a href="https://github.com/google/closure-compiler/wiki/Generic-Types" rel="noopener noreferrer" target="_blank">Google Closure Compiler page on the topic</a>.</p>



<h3>Type casting</h3>



<p>While strong typing is often very helpful, you may find that TypeScript&rsquo;s built-in expectations don&rsquo;t quite work for your use case. In that sort of instance, we might need to cast an object to a new type. One instance of when this might be necessary is when working with event listeners.</p>



<p>In TypeScript, all event listeners take a function as a callback where the first argument is an object of type <code>Event</code>, which has a property, target, that is an <code>EventTarget</code>. This <em>is</em> the correct type per the DOM standard, but oftentimes the bit of information we want out of the event&rsquo;s target doesn&rsquo;t exist on <code>EventTarget</code> &mdash; such as the value property that exists on <code>HTMLInputElement.prototype</code>. That makes the following code invalid:</p>



<pre rel="JavaScript"><code markup="tt">document.querySelector('input').addEventListener(event =&gt; {
&nbsp; console.log(event.target.value);
};</code></pre>



<p>TypeScript will complain that the property <code>value</code> doesn&rsquo;t exist on <code>EventTarget</code> even though we, as developers, know fully well that an <code>&lt;input&gt;</code> does have a <code>value</code>.</p>



<figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-30.png?resize=1024%2C215&amp;ssl=1" alt="A screenshot showing that value doesn&rsquo;t exist on type EventTarget" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-30.png?resize=1024%2C215&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-30.png?resize=300%2C63&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-30.png?resize=768%2C161&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-30.png?resize=1000%2C210&amp;ssl=1 1000w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-30.png?w=1364&amp;ssl=1 1364w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>In order for us to tell TypeScript that we know <code>event.target</code> will be an <code>HTMLInputElement</code>, we must cast the object&rsquo;s type:</p>



<pre rel="JavaScript"><code markup="tt">document.getElementById('input').addEventListener('input', event =&gt; {
&nbsp; console.log(/** @type {HTMLInputElement} */(event.target).value);
});</code></pre>



<p>Wrapping <code>event.target</code> in parenthesis will set it apart from the call to <code>value</code>. Adding the type before the parenthesis will tell TypeScript we mean that the <code>event.target</code> is something different than what it ordinarily expects.</p>



<figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-31.png?resize=1024%2C101&amp;ssl=1" alt="Screenshot of a valid example of type casting in VS Code." srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-31.png?resize=1024%2C101&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-31.png?resize=300%2C30&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-31.png?resize=768%2C76&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-31.png?resize=1000%2C99&amp;ssl=1 1000w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/07/image-31.png?w=1356&amp;ssl=1 1356w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>And if a particular object is being problematic, we can always tell TypeScript an object is <code>@type {any}</code> to ignore error messages, although this is generally considered bad practice depsite being useful in a pinch.</p>



<h3>Wrapping up</h3>



<p>TypeScript is an incredibly powerful tool that many developers are using to streamline their workflow around consistent code standards. While most applications will utilize the built-in compiler, some projects might decide that the extra syntax that TypeScript provides gets in the way. Or perhaps they just feel more comfortable sticking to standards rather than being tied to an expanded syntax. In those cases, developers can still get the benefits of utilizing TypeScript&rsquo;s type system even while writing vanilla JavaScript.</p>
<hr><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/typescript-minus-typescript/" target="_blank">TypeScript, Minus TypeScript</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>
<p>You can support CSS-Tricks by being an <a href="https://css-tricks.com/product/mvp-supporter/" rel="noopener noreferrer" target="_blank">MVP Supporter</a>.</p>]]></content>
	<updated>2020-08-06T14:44:39+00:00</updated>
	<author><name>Caleb Williams</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-08-06T14:44:39+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>

	<category term="typescript"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-07-22:/408004</id>
	<link href="https://kottke.org/quick-links" rel="alternate" type="text/html"/>
	<title type="html">Seven Quick Links for Wednesday Afternoon</title>
	<summary type="html"><![CDATA[<p>A lovely remembrance of Robin Williams from someone who went to the same 12-step meeting f...</p>]]></summary>
	<content type="html"><![CDATA[<p><a href="https://twitter.com/msmacb/status/1285735240716124162" target="_blank" rel="noopener noreferrer">A lovely remembrance of Robin Williams from someone who went to the same 12-step meeting for years. "He kept a very low profile but he was unfailingly kind."</a> [twitter.com]</p><p><a href="https://lithub.com/still-stuck-at-home-read-these-7-books-in-which-very-little-happens/" target="_blank" rel="noopener noreferrer">Seven books to read in which very little happens. Includes Nicholson Baker's The Mezzanine, which is fantastic.</a> [lithub.com]</p><p><a href="https://www.nytimes.com/interactive/2020/07/21/sports/coronavirus-changes-baseball-nba-nfl.html" target="_blank" rel="noopener noreferrer">The sports writers at the NY Times have come up with some interesting ideas (good and bad) about how to fix professional sports (e.g. too-long baseball games, confusing rules in soccer &amp; football, boring golf).</a> [nytimes.com]</p><p><a href="https://www.nature.com/articles/s41586-020-2509-0" target="_blank" rel="noopener noreferrer">Whoa, a paper that presents evidence of possible human presence in Mexico "as early as 33,000&ndash;31,000 years ago", which is more than 15,000 years earlier than the Clovis culture.</a> [nature.com]</p><p><a href="https://www.inthepublicinterest.org/wp-content/uploads/ITPI_USPSPrivatization_July2020.pdf" target="_blank" rel="noopener noreferrer">The Billionaire Behind Efforts to Kill the U.S. Postal Service. Spoiler warning: it's libertarian dipshit Charles Koch, who has done untold damage to our country and the Earth.</a> [inthepublicinterest.org]</p><p><a href="https://www.youtube.com/watch?v=NSBxzFH9eLI" target="_blank" rel="noopener noreferrer">Billie Eilish's bad guy, but played in the major key. "instead of playing it normal i'm the good guy so i play it in the major key, to the screams of music majors everywhere."</a> [youtube.com]</p><p><a href="https://poets.org/poem/small-needful-fact" target="_blank" rel="noopener noreferrer">A Small Needful Fact, a poem by Ross Gay about Eric Garner. The last line of this knocked me on my ass.</a> [poets.org]</p>
			
			<p>---</p><p>Note: Quick Links are pushed to this RSS feed twice a day. For more immediate service, check out <a href="https://kottke.org" rel="noopener noreferrer" target="_blank">the front page of kottke.org</a>, <a href="https://kottke.org/quick-links" rel="noopener noreferrer" target="_blank">the Quick Links archive</a>, or <a href="https://twitter.com/kottke" rel="noopener noreferrer" target="_blank">the @kottke Twitter feed</a>.</p>]]></content>
	<updated>2020-07-22T21:52:01+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-07-22T21:52:01+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-07-07:/406995</id>
	<link href="https://kottke.org/20/07/the-indigenous-peruvian-trap-music-of-renata-flores" rel="alternate" type="text/html"/>
	<title type="html">The Indigenous Peruvian Trap Music of Renata Flores</title>
	<summary type="html"><![CDATA[<p>Quechua is an indigenous language family spoken by millions of people in the Andean region ...</p>]]></summary>
	<content type="html"><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Quechuan_languages" rel="noopener noreferrer" target="_blank">Quechua</a> is an indigenous language family spoken by millions of people in the Andean region of South America, primarily in Peru, Ecuador, and Bolivia. It was the main language of the Inca empire and today is the most widely spoken pre-Columbian language in the Americas. In her music, Peruvian singer/songwriter Renata Flores combines modern forms like hip hop, electronic, and trap music with native instruments and vocals sung in Quechua. Here&rsquo;s the video for one of her most popular songs, Tijeras:</p>

<p></p>

<p>Flores also does covers of pop songs (<a href="https://www.youtube.com/watch?v=0lIJdPdtzRU" rel="noopener noreferrer" target="_blank">Billie Eilish&rsquo;s Bad Guy</a>, <a href="https://www.youtube.com/watch?v=P_eErnh6P98" rel="noopener noreferrer" target="_blank">Fallin&rsquo; by Alicia Keys</a>) and she first captured people&rsquo;s online attention with a Quechua cover of Michael Jackson&rsquo;s The Way You Make Me Feel performed when she was 14 years old:</p>

<p></p>

<p>Rosa Ch&aacute;vez Yacila <a href="https://www.vice.com/en_us/article/ne84bg/renata-flores-brought-quechua-to-youtube-and-then-everything-changed" rel="noopener noreferrer" target="_blank">wrote an article for Vice about Flores and her music</a> last year. Her use of Quechua in pop music brought the language out of private spaces into the public.</p>

<blockquote><p>It&rsquo;s very common for many Quechua speakers to not teach their children or grandchildren the language because they consider this knowledge as a burden. To explain the shortage of active bilingualism in Peru, the linguist Virginia Zavala uses the concept of &ldquo;linguistic ideologies,&rdquo; which are the ideas that people have about languages. For example: French is the language of love; German sounds rough; Italian, Portuguese, and Spanish are similar.</p>

<p>Quechua, similarly to other indigenous languages, is associated with poverty, rural life, and illiteracy. These ideas have been shaped by history and society to the point that people hold on to these beliefs as if they were universal truths. And these &ldquo;truths&rdquo; are deeply embedded in their conscious thought process. Value hierarchies also exist with languages. Some are &ldquo;worth&rdquo; more than others.</p>

<p>The end result is that many native Quechua speakers believe that using Quechua in public is unnecessary after learning Spanish. Either by shyness or shame, they reserve their maternal tongue for private spaces and intimate conversations.</p></blockquote>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/language" rel="noopener noreferrer" target="_blank">language</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/music" rel="noopener noreferrer" target="_blank">music</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Renata%20Flores" rel="noopener noreferrer" target="_blank">Renata Flores</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Rosa%20Chavez%20Yacila" rel="noopener noreferrer" target="_blank">Rosa Chavez Yacila</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/video" rel="noopener noreferrer" target="_blank">video</a>]]></content>
	<updated>2020-07-07T22:07:27+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-07-07T22:07:27+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-06-01:/404596</id>
	<link href="https://goodevilgenius.org/2020/06/01/A-riot-is-the-language-of-the-unheard/" rel="alternate" type="text/html"/>
	<title type="html">A Riot is the Language of the Unheard</title>
	<summary type="html"><![CDATA[<p>&ldquo;A Riot is the Language of the Unheard&rdquo;Like many people, I&rsquo;ve been thinking a lot over the past few ...</p>]]></summary>
	<content type="html"><![CDATA[<h1><a href="https://tt-rss.goodevilgenius.org/#%E2%80%9Ca-riot-is-the-language-of-the-unheard%E2%80%9D" title="&ldquo;A Riot is the Language of the Unheard&rdquo;" rel="noopener noreferrer" target="_blank"></a>&ldquo;A Riot is the Language of the Unheard&rdquo;</h1><p>Like many people, I&rsquo;ve been thinking a lot over the past few days about race andlaw enforcement, and race relations in this country.</p><p>I grew up in a family where I was taught to respect others for who they are. Myparents never had to tell me that the respect I show others should not bedependent on that person&rsquo;s race, sex, religion, or lifestyle. They taught me torespect others, and showed me that that respect should apply to all.</p><p>I also didn&rsquo;t grow up with a lot of racism around me. I never experienced itmyself, and I don&rsquo;t recall, as a child, seeing others experience it. I think myexperience was somewhat unusual in that respect, but not as unusual as one mightthink.</p><p>Looking back on my childhood, I realize that I didn&rsquo;t know that much about race.I remember being very young, and thinking that there were two races: white andblack, and that everyone else was probably some mixture of the two. I eventuallylearned more as my experience increased. But as I learned more about race, Istill knew very little about racism.</p><p>I think I was na&iuml;ve in that regard. My impression of contemporary racism, as achild, was that a lot of people of my grandparents&rsquo; generation were racist. Andthere were pockets of ignorant people, like the Klan, who were still racisttoday. But other than that, I thought racism was part of our history.</p><p>This ignorance was part of my privilege of growing up white. Of course, as Igrew older, I realized that racism was still present. Racism still affectedpeople on a daily basis. But I still couldn&rsquo;t really appreciate it.</p><p>Nearly thirty years ago, when I was almost nine years old, Rodney King wasbeaten by LAPD officers. This incident was filmed, and became public knowledge.I don&rsquo;t actually remember it happening. I don&rsquo;t know if my parents intentionallytried to shield my from it, or if it never came up in conversation when I waspaying attention to them. But I&rsquo;m certain that black children, much younger thanI was, were aware of what happened.</p><p>I eventually learned about Rodney King. I was shocked and appalled when I didlearn. Like half of the country who learned about it, I couldn&rsquo;t believe that itcould happen. I say &ldquo;half of the country&rdquo; because the other half of the countryalready knew that this kind of thing happened. They&rsquo;ve seen it. They&rsquo;ve seenpolice intimidation and unnecessary force. For many of us, Rodney King was awake-up call, but for others of us who lived with this, it was yet anotherincident.</p><p>Six and a half years ago, I married an amazing, intelligent, and beautifulwoman, whose father is black. Over time, I&rsquo;ve heard from her about herexperiences with racism. She has seen it all her life. To me, it was anexceptional and awful thing. To her, it was an everyday (but still awful) thing.</p><p>As I&rsquo;ve learned more from her, I&rsquo;ve come to realize a few things:</p><ul><li>There&rsquo;s no way that I can fully understand her experience.</li><li>Even as I grew up and learned more, I was still woefully ignorant about how  people are affected by their own race every single day.</li><li>The only way for me, and people like me, to learn is to listen.</li></ul><p>The thing is, I could&rsquo;ve learned many of these lessons much sooner. I&rsquo;ve hadplenty of black and brown friends and associates throughout my life. I&rsquo;ve eventalked to some of them about their experiences with racism, but it had alwaysbeen somewhat superficial conversations. I had never really asked a black personto tell me about how their race affected their life.</p><p>But now I have had those conversations with my wife. I can&rsquo;t possibly fully feelwhat she has felt, but I understand a lot better. I can count on one hand thenumber of times I&rsquo;ve felt intimidated by a police officer (it&rsquo;s two, by theway). Many black and brown people can&rsquo;t even count on one hand the number oftimes in one month.</p><p>Mostly, I&rsquo;ve been pretty overwhelmed by all of this.</p><p><img src="https://lh3.googleusercontent.com/80ILoKmg3zEp95CgNLJchOQRJx7R29i5u8niZFP76YvirN99tUGScUVLef9s69YEQCAUJZQw9xrLDBs6RDYvjzqSn9AFLmGWp47fBVOGJ_5ieOn3pxal-y0Qgcakg7Eoqrol582DItQ=w505-h673-no" alt="Bi-racial child looking out a window" referrerpolicy="no-referrer"></p><p>This is my son. He&rsquo;s handsome, and kind, and so smart, and a little rebellious.He&rsquo;s also my only child who probably can&rsquo;t &ldquo;pass for white&rdquo;. And so, he&rsquo;s alsothe one I worry about the most. Right now, I can protect him. But twenty yearsfrom now, he could be jogging one night, when someone sees those big, beautifulcurls, and he becomes the next Ahmaud Arbery.</p><p><img src="https://lh3.googleusercontent.com/8Ddn1HYb0C4IXI--GVwIlO3oMaZoUa5dNitt2m1SbPD825rnaaDxeHPDOKDQfKQCqEge7bVjlFxuZJP-DWj88lRju-b5M9QIDWkzpI9PkXlVm5W166YM9CawgmtAQ8aep01SVybRUhU=w981-h673-no" alt='Post by Martin Luther King III: "As my father explained during his lifetime, a riot is the language of the unheard."' referrerpolicy="no-referrer"></p><p>The title of my post is a quote by the Reverend Martin Luther King, Jr. Iincluded this because I&rsquo;ve seen a lot of comments on social media fromwell-intentioned white folks who are complaining about the rioting, andreferring to those that are causing property damage as &ldquo;criminals&rdquo;, or as thePresident has said, &ldquo;thugs&rdquo;. It&rsquo;s hard to understand why someone might go from apeaceful protest to damaging buildings.</p><p>But these people are mad. They&rsquo;ve spent their lives being careful around policeofficers, to try and prevent one of them from losing it, and hurting them.They&rsquo;ve seen the tape of Rodney King being beaten, but it&rsquo;s not history forthem. They have continued to see it for the last thirty years since that time.And for most of them, nothing substantive has changed. And now, a black manjogging is killed. And then another black man, while trying to enjoy hisbird-watching hobby, politely asks a woman to put a leash on her dog, as she&rsquo;ssupposed to do, and is threatened by her as she claims she will lie to thepolice about him, and say he is threatening her life. She knows that saying thathe&rsquo;s African-American will result in a harsher response from the police.Finally, a man is killed by police, and when they protest, the President callsthem thugs.</p><p>They are no longer being heard. George Floyd was not heard by the police as hepled for them to let him breathe, and in his dying moments, cried out for hismother. They are not being heard by the people who need to hear them: thepolice, the mayors, the governors, and the President. They&rsquo;re scared thatthey&rsquo;ll accidentally jaywalk and end up like George Floyd. What are they goingto do? They won&rsquo;t be heard, so they are making themselves heard in the only waythey can figure out how.</p><p>Property can be rebuilt, but lives cannot be restored.</p><p><img src="https://lh3.googleusercontent.com/djTzCWhpqDzzNPoJhMaiSJRkAW_m3YHR2Urea3KJzcXmz5R1hLL3BoXy29gJ8bJmEv26_4Bo0XjblV44if59mFTF6SKKvagQiDWzB-YYcSQZ2GwyV0oFfo6WP-ppnWzWyGOQy6ZUl1c=w687-h673-no" alt="Post by Gandhi Mahal Restaurant regarding property damage during the riot" referrerpolicy="no-referrer"></p><p>If anyone reading this wants to criticize the rioters who are burning downpolice stations, I urge you, before you type that comment on a Facebook post,call up a black or brown friend and talk to them. Let them be heard by someone.</p>]]></content>
	<updated>2024-06-25T16:32:16+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://goodevilgenius.org</id>
		<link rel="self" href="https://goodevilgenius.org"/>
		<updated>2024-06-25T16:32:16+00:00</updated>
		<title>Dan's Musings</title></source>

	<category term="police"/>

	<category term="race relations"/>

	<category term="racism"/>

	<category term="riots"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-05-04:/402539</id>
	<link href="http://languagehat.com/how-to-understand-aliens/" rel="alternate" type="text/html"/>
	<title type="html">How to Understand Aliens.</title>
	<summary type="html"><![CDATA[<p>Last night, having just watched a documentary on the Connecticut River flood of 1936, my wife and I ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Last night, having just watched a <a href="https://www.pbs.org/video/wgby-documentaries-great-flood-1936/" rel="noopener noreferrer" target="_blank">documentary</a> on the Connecticut River flood of 1936, my wife and I discovered that our basement had flooded &mdash; apparently the sump pump had failed.  So this morning we called the Barstows (it&rsquo;s great to have contractors you can rely on in emergencies) and they sent a crew over within half an hour, unclogged the pump (&ldquo;you should have it checked every year&rdquo;), vacuumed the floor, and left.  I investigated and discovered that, although most of the boxes were up out of harm&rsquo;s way (a precaution we took after the last flood, a decade or so ago), there was a box of sf books and magazines that had gotten wet, so I brought it up, opened it, and set everything out to dry.  Part of the contents was a set of <em>Worlds of Tomorrow</em>, a companion magazine to <em>If</em> which I was buying in the mid-&rsquo;60s; I opened the <a href="http://www.isfdb.org/cgi-bin/pl.cgi?62317" rel="noopener noreferrer" target="_blank">January 1966 issue</a> at random and found in the table of contents &ldquo;How to Understand Aliens,&rdquo; by Robert M. W. Dixon.  &ldquo;<em>The</em> Robert M. W. Dixon?&rdquo; I thought, and sure enough, the Australianist linguist about whom I posted repeatedly in January 2006 (<a href="http://languagehat.com/memoirs-of-a-field-worker/" rel="noopener noreferrer" target="_blank">1</a>, <a href="http://languagehat.com/dixon-the-word-for-dog/" rel="noopener noreferrer" target="_blank">2</a>, <a href="http://languagehat.com/dixon-chloe-the-ideal-informant/" rel="noopener noreferrer" target="_blank">3</a>, <a href="http://languagehat.com/dixon-mother-in-law-language-i/" rel="noopener noreferrer" target="_blank">4</a>, <a href="http://languagehat.com/dixon-mother-in-law-language-ii/" rel="noopener noreferrer" target="_blank">5</a>, <a href="http://languagehat.com/dixon-mother-in-law-language-iii/" rel="noopener noreferrer" target="_blank">6</a>) had written both stories and articles for sf magazines back in those days.</p>
<p><del datetime="2020-05-04T18:24:46+00:00">I wish I could send you to an online version of &ldquo;How to Understand Aliens,&rdquo; but there doesn&rsquo;t seem to be one.  You can see the beginning in accursed snippet view <a href="https://www.google.com/books/edition/Worlds_of_Tomorrow/X5EsAQAAIAAJ?hl=en&amp;gbpv=1&amp;bsq=%22it+seems+likely+that,+when+space+exploration%22&amp;dq=%22it+seems+likely+that,+when+space+exploration%22&amp;printsec=frontcover" rel="noopener noreferrer" target="_blank">here</a>, but that&rsquo;s not much help.</del> You can read the article <a href="https://archive.org/details/Worlds_of_Tomorrow_v03n05_1966-01_dtsg0318.Anon/page/n113/mode/2up" rel="noopener noreferrer" target="_blank">here</a> (thanks, Owlmirror!).  I&rsquo;ll copy out a short passage that will give you some idea; the whole thing is well done, as one would expect, and hopefully gave some readers (and writers) a better idea of how language works:</p>
<blockquote><p>Space linguists could gain valuable practice in unravelling bizarre languages by having a preliminary workout on a terrestrial language before venturing into extra-terrestrial contact. For instance, a cadet  linguist thrown amongst a tribe of Australian Aborigines would be able to get an idea of the variety of similar meanings a word can have when he learned that <em>gargal</em> could mean, firstly, the upper part of the human arm as it meets the body; secondly, the lower part of a branch, where it meets the trunk of a tree; and thirdly, the mouth of a stream where it flows into a larger river. And of how the meaning of a word can be extended to apply to new situations: the word for &lsquo;hollow log&rsquo;, <em>maralu</em>, being taken over to apply to &lsquo;shirt&rsquo; when the aborigines first came into contact with white men wearing this novel garment.</p></blockquote>]]></content>
	<updated>2020-05-04T15:16:39+00:00</updated>
	<author><name>languagehat</name></author>
	<source>
		<id>http://languagehat.com</id>
		<link rel="self" href="http://languagehat.com"/>
		<updated>2020-05-04T15:16:39+00:00</updated>
		<title>languagehat.com</title></source>

	<category term="uncategorized"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-05-02:/402434</id>
	<link href="https://css-tricks.com/phuoc-nguyens-one-page-wonders/" rel="alternate" type="text/html"/>
	<title type="html">Phuoc Nguyen’s One Page Wonders</title>
	<summary type="html"><![CDATA[<p>I keep running across these super useful one page sites, and they keep being by the same person! Lik...</p>]]></summary>
	<content type="html"><![CDATA[<p>I keep running across these super useful one page sites, and they keep being by the same person! Like this one with <a href="https://htmldom.dev/" rel="noopener noreferrer" target="_blank">over 100 vanilla JavaScript DOM manipulation recipes</a>, this similar one <a href="https://1loc.dev/" rel="noopener noreferrer" target="_blank">full of one-liners</a>,  and this one with <a href="https://csslayout.io/" rel="noopener noreferrer" target="_blank">loads of layouts</a>. For that last one, making 91 icons for all those design patterns is impressive alone. High five, Phuoc. </p>
<p>This is my favorite sort of marketing. <em>Some</em> of the products <em>aren&rsquo;t</em> free, like the <a href="https://react-pdf-viewer.dev/" rel="noopener noreferrer" target="_blank">React PDF </a>&hellip; <a href="https://css-tricks.com/phuoc-nguyens-one-page-wonders/" rel="noopener noreferrer" target="_blank">Read article <span> &ldquo;Phuoc Nguyen&rsquo;s One Page Wonders&rdquo;</span></a></p>
<p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/phuoc-nguyens-one-page-wonders/" target="_blank">Phuoc Nguyen&rsquo;s One Page Wonders</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>]]></content>
	<updated>2020-05-02T13:42:48+00:00</updated>
	<author><name>Chris Coyier</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-05-02T13:42:48+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-04-17:/401244</id>
	<link href="https://css-tricks.com/better-form-inputs-for-better-mobile-user-experiences/" rel="alternate" type="text/html"/>
	<title type="html">Better Form Inputs for Better Mobile User Experiences</title>
	<summary type="html"><![CDATA[<p>Here&rsquo;s one simple, practical way to make apps perform better on mobile devices: always configure HT...</p>]]></summary>
	<content type="html"><![CDATA[<p>Here&rsquo;s one simple, practical way to make apps perform better on mobile devices: always configure HTML input fields with the correct <code>type</code>, <code>inputmode</code>, and <code>autocomplete</code> attributes. While these three attributes are often discussed in isolation, they make the most sense in the context of mobile user experience when you think of them as a team.&nbsp;</p>



<p>There&rsquo;s no question that forms on mobile devices can be time-consuming and tedious to fill in, but by properly configuring inputs, we can ensure that the data entry process is as seamless as possible for our users. Let&rsquo;s take a look at some examples and best practices we can use to create better user experiences on mobile devices.</p>



<span></span>



<figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?fit=1024%2C842&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?w=2408&amp;ssl=1 2408w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?resize=300%2C247&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?resize=1024%2C842&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?resize=768%2C631&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?resize=1536%2C1263&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?resize=2048%2C1684&amp;ssl=1 2048w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/qyjE-kzw.png?resize=1000%2C822&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption><a href="https://better-mobile-inputs.netlify.com/" rel="noopener noreferrer" target="_blank">Use this demo</a> to experiment on your own, if you&rsquo;d like.</figcaption></figure><h3>Using the correct input type</h3>



<p>This is the easiest thing to get right. Input types, like <code>email</code>, <code>tel</code>, and <code>url</code>, are well-supported across browsers. While the benefit of using a type, like <code>tel</code> over the more generic <code>text</code>, might be hard to see on desktop browsers, it&rsquo;s immediately apparent on mobile. </p>



<p>Choosing the appropriate type changes the keyboard that pops up on Android and iOS devices when a user focuses the field. For very little effort, just by using the right type, we will show custom keyboards for <a href="https://better-mobile-inputs.netlify.com/?type=email" rel="noopener noreferrer" target="_blank">email</a>, <a href="https://better-mobile-inputs.netlify.com/?type=tel" rel="noopener noreferrer" target="_blank">telephone numbers</a>, <a href="https://better-mobile-inputs.netlify.com/?type=url" rel="noopener noreferrer" target="_blank">URLs</a>, and even <a href="https://better-mobile-inputs.netlify.com/?type=search" rel="noopener noreferrer" target="_blank">search inputs</a>.&nbsp;</p>



<figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?fit=1024%2C405&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?w=1749&amp;ssl=1 1749w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?resize=300%2C119&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?resize=1024%2C405&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?resize=768%2C303&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?resize=1536%2C607&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-text.png?resize=1000%2C395&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Text input type on iOS&nbsp;(left)&nbsp;and Android&nbsp;(right)</figcaption></figure><figure><img src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?fit=1024%2C405&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?w=1749&amp;ssl=1 1749w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?resize=300%2C119&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?resize=1024%2C405&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?resize=768%2C303&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?resize=1536%2C607&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-email.png?resize=1000%2C395&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Email input type on iOS&nbsp;(left)&nbsp;and Android&nbsp;(right)</figcaption></figure><figure><img src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?fit=1024%2C405&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?w=1749&amp;ssl=1 1749w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?resize=300%2C119&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?resize=1024%2C405&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?resize=768%2C303&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?resize=1536%2C607&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-url.png?resize=1000%2C395&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>URL input type on iOS&nbsp;(left)&nbsp;and Android&nbsp;(right)</figcaption></figure><figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?fit=1024%2C405&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?w=1749&amp;ssl=1 1749w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?resize=300%2C119&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?resize=1024%2C405&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?resize=768%2C303&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?resize=1536%2C607&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-search.png?resize=1000%2C395&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Search input type on iOS&nbsp;(left)&nbsp;and Android&nbsp;(right)</figcaption></figure><p>One thing to note is that both <code>input type="email"</code> and <code>input type="url"</code> come with validation functionality, and modern browsers will show an error tooltip if their values do not match the expected formats when the user submits the form. If you&rsquo;d rather turn this functionality off, you <a href="https://codepen.io/aholachek/pen/poJMXpj" rel="noopener noreferrer" target="_blank">can simply add the <code>novalidate</code> attribute to the containing <code>form</code></a>.</p>



<h3>A quick detour into date types</h3>



<p>HTML inputs comprise far more than specialized text inputs &mdash; you also have radio buttons, checkboxes, and so on. For the purposes of this discussion, though, I&rsquo;m mostly talking about the more <strong>text-based inputs</strong>.&nbsp;</p>



<p>There is a type of input that sits in the liminal space between the more free-form text inputs and input widgets like radio buttons: <strong><code>date</code></strong>. The <code>date</code> input type comes in a variety of flavors that are well-supported on mobile, including <a href="https://better-mobile-inputs.netlify.com/?type=date" rel="noopener noreferrer" target="_blank"><code>date</code></a>, <a href="https://better-mobile-inputs.netlify.com/?type=time" rel="noopener noreferrer" target="_blank"><code>time</code></a>, <a href="https://better-mobile-inputs.netlify.com/?type=datetime-local" rel="noopener noreferrer" target="_blank"><code>datetime-local</code></a>, and <a href="https://better-mobile-inputs.netlify.com/?type=month" rel="noopener noreferrer" target="_blank"><code>month</code></a>. These pop up custom widgets in iOS and Android when they are focused. Instead of triggering a specialized keyboard, they show a select-like interface in iOS, and various different types of widgets on Android (where the <a href="https://better-mobile-inputs.netlify.com/?android=true&amp;autocomplete&amp;inputmode&amp;type=date" rel="noopener noreferrer" target="_blank"><code>date</code></a> and <a href="https://better-mobile-inputs.netlify.com/?android=true&amp;autocomplete&amp;inputmode&amp;type=time" rel="noopener noreferrer" target="_blank"><code>time</code></a> selectors are particularly slick).&nbsp;</p>



<p>I was excited to start using native defaults on mobile, until I looked around and realized that most major apps and mobile websites use custom date pickers rather than native date input types.&nbsp;There could be a couple reasons for this. First, I find the native iOS date selector to be less intuitive than a calendar-type widget. Second, even the beautifully-designed Android implementation is fairly limited compared to custom components &mdash; there&rsquo;s no easy way to input a date range rather than a single date, for instance.&nbsp;</p>



<p>Still, the date input types are worth checking out if the custom datepicker you&rsquo;re using doesn&rsquo;t perform well on mobile. If you&rsquo;d like to try out the native input widgets on iOS and Android while making sure that desktop users see a custom widget instead of the default dropdown, this snippet of CSS <a href="https://codepen.io/aholachek/pen/RwPXjWJ?editors=1100" rel="noopener noreferrer" target="_blank">will hide the calendar dropdown</a> for desktop browsers that implement it:</p>



<pre rel="CSS"><code markup="tt">::-webkit-calendar-picker-indicator {
&nbsp; display: none;
}</code></pre>



<figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-date.png?fit=1024%2C521&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-date.png?w=1315&amp;ssl=1 1315w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-date.png?resize=300%2C153&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-date.png?resize=1024%2C521&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-date.png?resize=768%2C391&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-date.png?resize=1000%2C509&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Date input type on iOS&nbsp;(left)&nbsp;and Android&nbsp;(right)</figcaption></figure><figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-time.png?fit=1024%2C486&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-time.png?w=1411&amp;ssl=1 1411w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-time.png?resize=300%2C142&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-time.png?resize=1024%2C486&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-time.png?resize=768%2C364&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-time.png?resize=1000%2C474&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Time input type on iOS&nbsp;(left)&nbsp;and Android&nbsp;(right)</figcaption></figure><p>One final thing to note is that <code>date</code> types cannot be overridden by the <code>inputmode</code> attribute, which we&rsquo;ll discuss next.</p>



<h3>Why should I care about inputmode?</h3>



<p>The <code>inputmode</code> attribute allows you to override the mobile keyboard specified by the input&rsquo;s type and directly declare the type of keyboard shown to the user. When I first learned about this attribute, I wasn&rsquo;t impressed &mdash; why not just use the correct <code>type</code> in the first place? But while <code>inputmode</code> is often unnecessary, there are a few places where the attribute can be extremely helpful. The most notable use case that I&rsquo;ve found for <code>inputmode</code> is building a better number input.</p>



<p>While some HTML5 input types, like <code>url</code> and <code>email</code>, are straightforward, <code>input type="number"</code> is a different matter. It has some <a href="https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/" rel="noopener noreferrer" target="_blank">accessibility concerns </a>as well as a somewhat awkward UI. For example, desktop browsers, like Chrome, show tiny increment arrows that are easy to trigger accidentally by scrolling.</p>



<p>So here&rsquo;s a pattern to memorize and use going forwards. For most numeric inputs, instead of using this:&nbsp;</p>



<pre rel="HTML"><code markup="tt">&lt;input type="number" /&gt;</code></pre>



<p>&hellip;you actually want to use this:</p>



<pre rel="HTML"><code markup="tt">&lt;input type="text" inputmode="decimal" /&gt;</code></pre>



<p>Why not <code>inputmode="numeric"</code> instead of <code>inputmode="decimal"</code> ?&nbsp;</p>



<p>The <code>numeric</code> and <code>decimal</code> attribute values produce identical keyboards on Android. On iOS, however, <code>numeric</code> displays a keyboard that shows both numbers and punctuation, while <code>decimal</code> shows a focused grid of numbers that almost looks exactly like the <code>tel</code> input type, only without extraneous telephone-number focused options. That&rsquo;s why it&rsquo;s my preference for most types of number inputs.</p>



<figure><img src="https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?fit=1024%2C424&amp;ssl=1" alt="" srcset="https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?w=1615&amp;ssl=1 1615w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?resize=300%2C124&amp;ssl=1 300w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?resize=1024%2C424&amp;ssl=1 1024w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?resize=768%2C318&amp;ssl=1 768w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?resize=1536%2C636&amp;ssl=1 1536w, https://i2.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-ios.png?resize=1000%2C414&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>iOS&nbsp;<code>numeric</code>&nbsp;input&nbsp;(left)&nbsp;and&nbsp;<code>decimal</code>&nbsp;input&nbsp;(right)</figcaption></figure><figure><img src="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?fit=1024%2C315&amp;ssl=1" alt="" srcset="https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?w=1615&amp;ssl=1 1615w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?resize=300%2C92&amp;ssl=1 300w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?resize=1024%2C315&amp;ssl=1 1024w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?resize=768%2C236&amp;ssl=1 768w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?resize=1536%2C473&amp;ssl=1 1536w, https://i1.wp.com/css-tricks.com/wp-content/uploads/2020/04/input-numeric-android.png?resize=1000%2C308&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"><figcaption>Android&nbsp;<code>numeric</code>&nbsp;input&nbsp;(left)&nbsp;and&nbsp;<code>decimal</code>&nbsp;input&nbsp;(right)</figcaption></figure><p>Christian Oliff has written&nbsp;<a rel="noopener noreferrer" target="_blank" href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/">an excellent article</a>&nbsp;dedicated solely to the&nbsp;inputmode&nbsp;attribute.</p>



<h3>Don&rsquo;t forget autocomplete</h3>



<p>Even more important than showing the correct mobile keyboard is showing helpful autocomplete suggestions. That can go a long way towards creating a faster and less frustrating user experience on mobile.</p>



<p>While browsers have heuristics for showing autocomplete fields, you cannot rely on them, and should still be sure to add the correct <code>autocomplete</code> attribute. For instance, in iOS Safari, I found that an <code>input type="tel"</code> would only show autocomplete options if I <a href="https://better-mobile-inputs.netlify.com/?autocomplete=tel&amp;type=tel" rel="noopener noreferrer" target="_blank">explicitly added a <code>autocomplete="tel"</code> attribute</a>.</p>



<p>You may think that you are familiar with the basic <code>autocomplete</code> options, such as those that help the user fill in credit card numbers or address form fields, but I&rsquo;d urge you to <a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html%23inappropriate-for-the-control" rel="noopener noreferrer" target="_blank">review them</a> to make sure that you are aware of all of the options. The spec lists over 50 values! Did you know that <code>autocomplete="one-time-code"</code> can make a phone verification user flow super smooth?</p>



<figure><video controls muted src="https://css-tricks.com/wp-content/uploads/2020/04/take-2.mp4" playsinline></video></figure><h3>Speaking of autocomplete&hellip;</h3>



<p>I&rsquo;d like to mention one final element that allows you to create your own custom autocomplete functionality: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist" rel="noopener noreferrer" target="_blank"><code>datalist</code></a>. While it creates a serviceable &mdash; if somewhat basic &mdash; autocomplete experience on desktop Chrome and Safari, it shines on iOS by surfacing suggestions in a convenient row right above the keyboard, where the system autocomplete functionality usually lives. Further, it allows the user to toggle between text and select-style inputs.</p>



<figure><video controls src="https://css-tricks.com/wp-content/uploads/2020/04/RPReplay_Final1585510573.mp4"></video></figure><p>On Android, on the other hand, <code>datalist</code> creates a more typical autocomplete dropdown, with the area above the keyboard reserved for the system&rsquo;s own typeahead functionality. One possible advantage to this style is that the dropdown list is easily scrollable, creating immediate access to all possible options as soon as the field is focused. (In iOS, in order to view more than the top three matches, the user would have to trigger the select picker by pressing the down arrow icon.)</p>



<figure><img src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?fit=576%2C1024&amp;ssl=1" alt="" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?w=1080&amp;ssl=1 1080w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?resize=169%2C300&amp;ssl=1 169w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?resize=576%2C1024&amp;ssl=1 576w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?resize=768%2C1365&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?resize=864%2C1536&amp;ssl=1 864w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/EyP4s7cA.png?resize=1000%2C1778&amp;ssl=1 1000w" sizes="(min-width: 735px) 864px, 96vw" referrerpolicy="no-referrer"></figure><p>You can use this demo to play around with&nbsp;<code>datalist</code>:</p>



<div></div>



<p>And you can explore all the autocomplete options, as well as input&nbsp;<code>type</code>&nbsp;and&nbsp;<code>inputmode</code>&nbsp;values, <a href="https://better-mobile-inputs.netlify.com/" rel="noopener noreferrer" target="_blank">using&nbsp;this tool I made</a> to help you quickly preview various input configurations on mobile.</p>



<h1>In summary</h1>



<p>When I&rsquo;m building a form, I&rsquo;m often tempted to focus on perfecting the desktop experience while treating the mobile web as an afterthought. But while it does take a little extra work to ensure forms work well on mobile, it doesn&rsquo;t have to be too difficult. Hopefully, this article has shown that with a few easy steps, you can make forms much more convenient for your users on mobile devices.</p>
<p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/better-form-inputs-for-better-mobile-user-experiences/" target="_blank">Better Form Inputs for Better Mobile User Experiences</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>]]></content>
	<updated>2020-04-17T15:08:17+00:00</updated>
	<author><name>Alex Holachek</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-04-17T15:08:17+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-04-07:/400486</id>
	<link href="https://about.gitlab.com/blog/2020/04/07/15-git-tips-improve-workflow/" rel="alternate" type="text/html"/>
	<title type="html">15 Git tips to improve your workflow</title>
	<summary type="html"><![CDATA[<p>This year, Git celebrates its 15th anniversary, and we&rsquo;ve been excitedly posting some thoughts about...</p>]]></summary>
	<content type="html"><![CDATA[<p>This year, <a href="https://git-scm.com/" rel="noopener noreferrer" target="_blank">Git</a> celebrates its 15th anniversary, and we&rsquo;ve been excitedly posting some thoughts about its creation and impact &mdash; from sharing our experience at <a href="https://tt-rss.goodevilgenius.org/blog/2020/03/25/git-merge-fifteen-year-git-party/" rel="noopener noreferrer" target="_blank">Git Merge 2020</a>, discussing <a href="https://tt-rss.goodevilgenius.org/blog/2020/03/05/what-is-gitlab-flow/" rel="noopener noreferrer" target="_blank">the problem with Git flow</a>, or highlighting the newest Git feature <a href="https://tt-rss.goodevilgenius.org/blog/2020/03/13/partial-clone-for-massive-repositories/" rel="noopener noreferrer" target="_blank">Partial Clone</a>.</p>

<p>Whether you&rsquo;re just getting started with Git, or you know your way around a command line, it&rsquo;s always nice to brush up on your skills, which is why we&rsquo;ve gathered 15 methods to improve your Git-based workflow.</p>

<h3>1. Git aliases</h3>

<p>One of the most impactful ways to improve your daily workflow is to create aliases for common commands to save you some time in the terminal.</p>

<p>You can use the following commands to create aliases for the most-used Git commands, <code>checkout</code>, <code>commit</code> and <code>branch</code>.</p>

<div><pre><code>git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
</code></pre></div>
<p>Instead of typing <code>git checkout master</code>, you only need to type <code>git co master</code>.</p>

<p>You could also edit these commands or add more by modifying the <code>~/.gitconfig</code> file directly:</p>

<div><pre><code>[alias]
    co = checkout
    ci = commit
    br = branch
</code></pre></div>
<h3>2. See the repository status in your terminal&rsquo;s prompt</h3>

<p>If you&rsquo;d like to visualize the status of your repository, you can run <code>git-prompt.sh</code>
(you can <a href="https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh" rel="noopener noreferrer" target="_blank">download it</a> and follow the
instructions to use it in your system). If you're using Linux
and have installed Git with your package manager, it may already be
present on your system, likely under <code>/etc/bash_completion.d/</code>.</p>

<p>You can replace your standard shell prompt with something a bit more exciting:</p>

<p><img src="https://tt-rss.goodevilgenius.org/images/blogimages/git-tricks/git-shell-info.png" alt="Git shell prompt" referrerpolicy="no-referrer"></p>

<p><em>Taken from oh-my-zsh's <a href="https://github.com/robbyrussell/oh-my-zsh/wiki/Themes#kafeitu" rel="noopener noreferrer" target="_blank">themes wiki</a>.</em></p>

<h3>3. Compare commits from the command line</h3>

<p>A simple way to compare the differences between commits or versions of the same file is to use the <code>git diff</code> command.</p>

<p>If you want to compare the same file between different commits, you run the following:</p>

<div><pre><code>$ git diff $start_commit..$end_commit -- path/to/file
</code></pre></div>
<p>If you want to compare the changes between two commits:</p>

<div><pre><code>$ git diff $start_commit..$end_commit
</code></pre></div>
<p>These commands will open the diff view inside the terminal, but if you prefer to use a more visual tool to compare your diffs, you can use <code>git difftool</code>. <a href="https://meldmerge.org/" rel="noopener noreferrer" target="_blank">Meld</a> is a useful viewer/editor to visually compare diffs.</p>

<p>To configure Meld:</p>

<div><pre><code>$ git config --global diff.tool git-meld
</code></pre></div>
<p>To start viewing the diffs:</p>

<div><pre><code>$ git difftool $start_commit..$end_commit -- path/to/file
# or
$ git difftool $start_commit..$end_commit
</code></pre></div>
<h3>4. Stashing uncommitted changes</h3>

<p>If you&rsquo;re ever working on a feature and need to do an emergency fix on the project, you could run into a problem. You don&rsquo;t want to commit an unfinished feature, and you also don&rsquo;t want to lose current changes. The solution is to temporarily remove these changes with the Git stash command:</p>

<div><pre><code>$ git stash
</code></pre></div>
<p>The git stash command hides changes, giving you a clean working directory and the ability to switch to a new branch to make updates, without having to commit a meaningless snapshot in order to save the current state.</p>

<p>Once you&rsquo;re done working on a fix and want to revisit your previous changes, you can run:</p>

<div><pre><code>$ git stash pop
</code></pre></div>
<p>And your changes will be recovered. &#65533;</p>

<p>If you no longer need those changes and want to clear the stash stack, you can do so with:</p>

<div><pre><code>$ git stash drop
</code></pre></div>
<h3>5. Pull frequently</h3>

<p>If you&rsquo;re using <a href="https://tt-rss.goodevilgenius.org/solutions/gitlab-flow/" rel="noopener noreferrer" target="_blank">GitLab Flow</a>, then you&rsquo;re working
on feature branches. Depending on how long your feature takes to implement, there might be several changes made to the master branch. In order to avoid major conflicts, you should frequently pull the changes from the master branch to your branch to resolve any conflicts as soon as possible and to make merging your branch to master easier.</p>

<h3>6. Autocomplete commands</h3>

<p>Using <a href="https://github.com/git/git/tree/master/contrib/completion" rel="noopener noreferrer" target="_blank">completion scripts</a>, you can quickly create the commands for <code>bash</code>, <code>tcsh</code> and <code>zsh</code>. If you want to type <code>git pull</code>, you can type just the first letter with <code>git p</code> followed by <kbd>Tab</kbd> will show the following:</p>

<div><pre><code>pack-objects   -- create packed archive of objects
pack-redundant -- find redundant pack files
pack-refs      -- pack heads and tags for efficient repository access
parse-remote   -- routines to help parsing remote repository access parameters
patch-id       -- compute unique ID for a patch
prune          -- prune all unreachable objects from the object database
prune-packed   -- remove extra objects that are already in pack files
pull           -- fetch from and merge with another repository or local branch
push           -- update remote refs along with associated objects
</code></pre></div>
<p>To show all available commands, type <code>git</code> in your terminal followed by
<kbd>Tab</kbd>+ <kbd>Tab</kbd>.</p>

<h3>7. Set a global <code>.gitignore</code></h3>

<p>If you want to avoid committing files like <code>.DS_Store</code> or Vim <code>swp</code> files,
you can set up a global <code>.gitignore</code> file.</p>

<p>Create the file:</p>

<div><pre><code>touch ~/.gitignore
</code></pre></div>
<p>Then run:</p>

<div><pre><code>git config <span>--global</span> core.excludesFile ~/.gitignore
</code></pre></div>
<p>Or manually add the following to your <code>~/.gitconfig</code>:</p>

<div><pre><code><span>[core]</span>
  <span>excludesFile</span> <span>=</span> <span>~/.gitignore</span>
</code></pre></div><p>You can create a list of the things you want Git to ignore. To learn more, visit the <a href="https://git-scm.com/docs/gitignore" rel="noopener noreferrer" target="_blank">gitignore documentation</a>.</p>

<h3>8. Enable Git&rsquo;s autosquash feature by default</h3>

<p>Autosquash makes it easier to squash commits during an interactive rebase. It can be enabled for each rebase using <code>git rebase -i --autosquash</code>, but it's easier to turn it on by default.</p>

<div><pre><code>git config <span>--global</span> rebase.autosquash <span>true</span>
</code></pre></div>
<p>Or manually add the following to your <code>~/.gitconfig</code>:</p>

<div><pre><code><span>[rebase]</span>
  <span>autosquash</span> <span>=</span> <span>true</span>
</code></pre></div>
<h3>9. Delete local branches that have been removed from remote on fetch/pull</h3>

<p>You likely have stale branches in your local repository that no longer exist in the remote one. To delete them in each fetch/pull, run:</p>

<div><pre><code>git config <span>--global</span> fetch.prune <span>true</span>
</code></pre></div>
<p>Or manually add the following to your <code>~/.gitconfig</code>:</p>

<div><pre><code><span>[fetch]</span>
  <span>prune</span> <span>=</span> <span>true</span>
</code></pre></div>
<h3>10. Use Git blame more efficiently</h3>

<p>Git blame is a handy way to discover who changed a line in a file. Depending on what you want to show, you can pass different flags:</p>

<div><pre><code>$ git blame -w  # ignores white space
$ git blame -M  # ignores moving text
$ git blame -C  # ignores moving text into other files
</code></pre></div>
<h3>11. Add an alias to check out merge requests locally</h3>

<p>A <a href="https://docs.gitlab.com/ee/user/project/merge_requests/" rel="noopener noreferrer" target="_blank">merge request</a> contains all the history from a repository, and the additional
commits added to the branch associated with the MR. You can check out a public merge request locally even if the source project is a fork (even a private fork) of the target project.</p>

<p>To check out a merge request locally, add the following alias to your <code>~/.gitconfig</code>:</p>

<div><pre><code>[alias]
  mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 &amp;&amp; git checkout mr-$1-$2' -
</code></pre></div>
<p>Now you can check out a merge request from any repository and any remote. For example, to check out the merge request with ID 5 as shown in GitLab
from the <code>upstream</code> remote, run:</p>

<div><pre><code>git mr upstream 5
</code></pre></div>
<p>This will fetch the merge request into a local <code>mr-upstream-5</code> branch and check
it out. In the above example, <code>upstream</code> is the remote that points to GitLab
which you can find out by running <code>git remote -v</code>.</p>

<h3>12. An alias of <code>HEAD</code></h3>

<p>Breaking news: <code>@</code> is the same as <code>HEAD</code>. Using it during a rebase is a lifesaver:</p>

<div><pre><code>git rebase <span>-i</span> @~2
</code></pre></div>
<h3>13. Resetting files</h3>

<p>You&rsquo;re modifying your code when you suddenly realize that the changes you made are not great, and you&rsquo;d like to reset them. Rather than clicking undo on everything you edited, you can reset your files to the HEAD of the branch:</p>

<div><pre><code>$ git reset --hard HEAD
</code></pre></div>
<p>Or if you want to reset a single file:</p>

<div><pre><code>$ git checkout HEAD -- path/to/file
</code></pre></div>
<p>Now, if you already committed your changes, but still want to revert back, you can use:</p>

<div><pre><code>$ git reset --soft HEAD~1
</code></pre></div>
<h3>14. The <code>git-open</code> plugin</h3>

<p>If you&rsquo;d like to quickly visit the website that hosts the repository you&rsquo;re on, you&rsquo;ll need <code>git-open</code>.</p>

<p><a href="https://github.com/paulirish/git-open#installation" rel="noopener noreferrer" target="_blank">Install it</a> and take it for a spin by cloning a repository from
<a href="https://gitlab.com/explore" rel="noopener noreferrer" target="_blank">GitLab.com</a>. From your terminal, navigate to the
repository and run <code>git open</code> to be transferred to the project&rsquo;s page on
GitLab.com.</p>

<p>The plugin works by default for projects hosted on GitLab.com, but you can also use it
with your own GitLab instances. In that case, set up the domain name with:</p>

<div><pre><code>git config gitopen.gitlab.domain git.example.com
</code></pre></div>
<p>You can open different remotes and branches if they have been set up. You can learn more by checking out the <a href="https://github.com/paulirish/git-open#examples" rel="noopener noreferrer" target="_blank">examples section</a>.</p>

<h3>15. The <code>git-extras</code> plugin</h3>

<p>If you want to elevate Git with more commands, try out the
<a href="https://github.com/tj/git-extras" rel="noopener noreferrer" target="_blank"><code>git-extras</code> plugin</a>, which includes <code>git info</code> (show
information about the repository) and <code>git effort</code> (number of commits per file).</p>

<h2>Learn more about Git</h2>

<p>We&rsquo;re excited to announce that <a href="https://tt-rss.goodevilgenius.org/company/team/#brendan" rel="noopener noreferrer" target="_blank">Brendan O&rsquo;Leary</a>, senior developer evangelist, will create 15 videos to celebrate Git's anniversary over the next several months. He&rsquo;ll focus on a variety of topics, from rebasing and merging to cherry-picking and branching. Take a look at the first video in the series. &#65533;</p>

<!-- blank line -->
<figure>
</figure><!-- blank line --><p>Cover image by <a href="https://unsplash.com/@brookelark?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" rel="noopener noreferrer" target="_blank">Brooke Lark</a> on <a href="https://unsplash.com/s/photos/birthday?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" rel="noopener noreferrer" target="_blank">Unsplash</a></p>
<img src="https://about.gitlab.com/images/blogimages/gitbirthday.jpg" referrerpolicy="no-referrer">]]></content>
	<updated>2020-04-07T00:00:00+00:00</updated>
	<author><name>Suri Patel</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2020-04-07T00:00:00+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-04-04:/400292</id>
	<link href="https://kottke.org/20/04/hbo-is-streaming-dozens-of-shows-movies-for-free-in-april" rel="alternate" type="text/html"/>
	<title type="html">HBO Is Streaming Dozens of Shows &amp; Movies for Free in April</title>
	<summary type="html"><![CDATA[<p>During the month of April, HBO is streaming dozens of shows, documentaries &amp; movies for f...</p>]]></summary>
	<content type="html"><![CDATA[<p><img src="https://tt-rss.goodevilgenius.org/plus/misc/images/wire-chess.jpg" border="0" alt="Wire Chess" referrerpolicy="no-referrer"></p>

<p>During the month of April, <a href="https://pressroom.warnermediagroup.com/us/media-release/hbo-0/stayhomeboxoffice/hbo-encourages-viewers-stayhomeboxoffice-hundreds-hours-free-programming" rel="noopener noreferrer" target="_blank">HBO is streaming dozens of shows, documentaries &amp; movies for free via HBO GO &amp; HBO NOW</a>. Shows include The Wire (my personal fave), Succession, VEEP, The Sopranos, True Blood, Six Feet Under, and Silicon Valley. HBO usually offers a few things outside their paywall, but never anything this extensive.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/HBO" rel="noopener noreferrer" target="_blank">HBO</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/The%20Wire" rel="noopener noreferrer" target="_blank">The Wire</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/TV" rel="noopener noreferrer" target="_blank">TV</a>]]></content>
	<updated>2020-04-04T21:34:58+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-04-04T21:34:58+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-31:/399949</id>
	<link href="https://kottke.org/20/03/people-behave-more-cooperatively-during-disasters" rel="alternate" type="text/html"/>
	<title type="html">People Behave More Cooperatively During Disasters</title>
	<summary type="html"><![CDATA[<p>I&rsquo;ve been wanting to write something about this for a few weeks now, so I was glad to...</p>]]></summary>
	<content type="html"><![CDATA[<p>I&rsquo;ve been wanting to write something about this for a few weeks now, so I was glad to find <a href="https://twitter.com/dgardner/status/1243144205167362048" rel="noopener noreferrer" target="_blank">this short but meaty Twitter thread by Dan Gardner</a> about how people react in a crisis: they get more cooperative, not less.</p>

<blockquote><p>Please remember: The idea that when disaster strikes people panic and social order collapses is very popular. It is also a myth. A huge research literature shows disaster makes people *more* pro-social. They cooperate. They support each other. They&rsquo;re better than ever.</p>

<p>But the myth matters because it can lead people to take counterproductive actions and adopt policies. The simple truth is we are a fantastically social species and threats only fuel our instinct to pro-social behaviour.</p>

<p>Incidentally, this point is made, and is forgotten, after every disaster. Remember 9/11? Everyone was astonished that snarling, greedy, individualistic New Yorkers were suddenly behaving like selfless saints. No need for surprise. That&rsquo;s humanity. That&rsquo;s how we roll.</p></blockquote>

<p>A reader suggested I check out <a href="http://rebeccasolnit.net/" rel="noopener noreferrer" target="_blank">Rebecca Solnit</a>&rsquo;s writing on the topic, and indeed she wrote an entire book in 2010 about this: <a href="http://www.amazon.com/exec/obidos/ASIN/B003XQEVLM/ref=nosim/0sil8" rel="noopener noreferrer" target="_blank">A Paradise Built in Hell: The Extraordinary Communities That Arise in Disaster</a>. <a href="https://www.cbc.ca/radio/thesundayedition/the-sunday-edition-for-march-22-2020-1.5500395/in-disasters-most-people-are-altruistic-brave-communitarian-generous-says-rebecca-solnit-1.5500410" rel="noopener noreferrer" target="_blank">Solnit recently spoke to CBC Radio</a> about her research.</p>

<blockquote><p>I had learned by reading the oral histories of the 1906 earthquake, and by reading the wonderful disaster sociologists in a field that begins in part with Samuel Prince, looking at the Halifax Explosion in 1917 &hellip; that actually in disasters, most people are altruistic, brave, communitarian, generous and deeply creative in rescuing each other, creating the conditions for success of survival and often creating these little disaster utopias where everyone feels equal. Everyone feels like a participant.</p>

<p>It&rsquo;s like a reset, when you turn the machine on and off and on again, that our basic default setting is generous and communitarian and altruistic. But what&rsquo;s shocking is the incredible joy people often seem to have, when they describe that sense of purpose, connection, community agency they found. It speaks to how deeply we desire something we mostly don&rsquo;t have in everyday life. That&rsquo;s a kind of social, public love and power, above and beyond the private life.</p></blockquote>

<p>I&rsquo;ve put <a href="https://onbeing.org/programs/rebecca-solnit-falling-together/" rel="noopener noreferrer" target="_blank">this 2016 episode of On Being with Solnit</a> on my to-listen list.</p>

<blockquote><p>The amazing thing about the 1989 earthquake &mdash; it was an earthquake as big as the kind that killed thousands of people in places like Turkey and Mexico City, and things like that. But partly, because we have good infrastructure, about 50 people died, a number of people lost their homes, everybody was shaken up. But what was so interesting for me was that people seemed to kind of love what was going on.</p></blockquote>

<p>That same year in the aftermath of the election, she wrote an essay called <a href="https://lithub.com/rebecca-solnit-how-to-survive-a-disaster/" rel="noopener noreferrer" target="_blank">How to Survive a Disaster</a>.</p>

<blockquote><p>I landed in Halifax, Nova Scotia, shortly after a big hurricane tore up the city in October of 2003.  The man in charge of taking me around told me about the hurricane-not the winds at more than a hundred miles an hour that tore up trees, roofs, telephone poles, not the seas that rose nearly ten feet, but the neighbors. He spoke of the few days when everything was disrupted and lit up with happiness as he did so. In his neighborhood all the people had come out of their houses to speak with each other, aid each other, to improvise a community kitchen, make sure the elders were okay, and spend time together, no longer strangers. &ldquo;Everybody woke up the next morning and everything was different,&rdquo; he mused. &ldquo;There was no electricity, all the stores were closed, no one had access to media. The consequence was that everyone poured out into the street to bear witness. Not quite a street party, but everyone out at once-it was a sense of happiness to see everybody even though we didn&rsquo;t know each other.&rdquo; His joy struck me powerfully.</p></blockquote>

<p>More reading material on this, via Gardner: <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2496928/" rel="noopener noreferrer" target="_blank">Disaster Mythology and Fact: Hurricane Katrina and Social Attachment</a>, <a href="http://www.csap.cam.ac.uk/media/uploads/files/1/10.1111-jasp.12176-disaster-myths-as-published-early-view.pdf" rel="noopener noreferrer" target="_blank">Psychological disaster myths in the perception and management of mass emergencies</a>, <a href="https://blogs.scientificamerican.com/observations/there-goes-hurricane-florence-here-come-the-disaster-myths/" rel="noopener noreferrer" target="_blank">There Goes Hurricane Florence; Here Come the Disaster Myths</a>, and <a href="http://disasterdoc.org/5-common-dangerous-disaster-myths/" rel="noopener noreferrer" target="_blank">5 Most Common (and Most Dangerous) Disaster Myths</a>.</p>

<p>Note: A version of this post <a href="https://mailchi.mp/kottke/bring-out-the-best-in-people-7731552" rel="noopener noreferrer" target="_blank">first appeared</a> in Noticing, the kottke.org newsletter. <a href="https://kottke.org/newsletter/" rel="noopener noreferrer" target="_blank">You can subscribe here</a>.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/A%20Paradise%20Built%20in%20Hell" rel="noopener noreferrer" target="_blank">A Paradise Built in Hell</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/books" rel="noopener noreferrer" target="_blank">books</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/COVID-19" rel="noopener noreferrer" target="_blank">COVID-19</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Dan%20Gardner" rel="noopener noreferrer" target="_blank">Dan Gardner</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Rebecca%20Solnit" rel="noopener noreferrer" target="_blank">Rebecca Solnit</a>]]></content>
	<updated>2020-03-31T14:57:13+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-03-31T14:57:13+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-27:/399721</id>
	<link href="https://mxb.dev/blog/emergency-website-kit/" rel="alternate" type="text/html"/>
	<title type="html">Emergency Website Kit</title>
	<summary type="html"><![CDATA[<p>Here&rsquo;s an outstanding idea from Max B&ouml;ck. He&rsquo;s created a boilerplate project for building websites ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Here&rsquo;s an outstanding idea from Max B&ouml;ck. He&rsquo;s created <a href="https://github.com/maxboeck/emergency-site" rel="noopener noreferrer" target="_blank">a boilerplate project</a> for building websites that fit within a single HTTP request. This is extremely important for websites that contain critical information for public safety. As Max writes:</p>



<blockquote><p>In cases of emergency,  many organizations need a quick way to publish critical information. But exisiting (CMS) websites are often unable to handle sudden spikes in  traffic.</p></blockquote>



<span></span>



<p>What&rsquo;s so special about this boilerplate? Well, it does smart stuff like:</p>



<ul><li>generates a static site using <a href="https://www.11ty.dev" rel="noopener noreferrer" target="_blank">Eleventy</a>,</li><li>uses minimal markup with inlined CSS,</li><li>aims to transmit everything in the first connection roundtrip (~14KB),</li><li>progressively enables offline-support with Service Workers,</li><li>uses <a href="https://www.netlifycms.org/" rel="noopener noreferrer" target="_blank">Netlify CMS</a> for easy content editing, and</li><li>provides one-click deployment via Netlify to get off the ground quickly</li></ul><p><a href="https://emergency-site.dev/" rel="noopener noreferrer" target="_blank">The example website</a> that Max built with this boilerplate is shockingly fast and I would go one step further to argue that <em>all websites</em> should feel as fast as this, not just websites that are useful in an emergency. </p>
<p><a href="https://mxb.dev/blog/emergency-website-kit/" title="Direct link to featured article" rel="noopener noreferrer" target="_blank">Direct Link to Article</a> &mdash; <a href="https://css-tricks.com/emergency-website-kit/" rel="noopener noreferrer" target="_blank">Permalink</a></p><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/emergency-website-kit/" target="_blank">Emergency Website Kit</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>]]></content>
	<updated>2020-03-27T21:12:55+00:00</updated>
	<author><name>Robin Rendle</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-03-27T21:12:55+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="eleventy"/>

	<category term="link"/>

	<category term="netlify"/>

	<category term="static sites"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-24:/399462</id>
	<link href="https://css-tricks.com/emojis-as-favicons/" rel="alternate" type="text/html"/>
	<title type="html">Emojis as Favicons</title>
	<summary type="html"><![CDATA[<p>Lea Verou had a dang genius idea to use an emoji as a favicon. The idea only recently possible as b...</p>]]></summary>
	<content type="html"><![CDATA[<p>Lea Verou had <a href="https://twitter.com/LeaVerou/status/1241619866475474946" rel="noopener noreferrer" target="_blank">a dang genius idea</a> to use an emoji as a favicon. The idea only recently possible as browsers have started supporting SVG for favicons. Chuck an emoji inside an SVG <code>&lt;text&gt;</code> element and use that as the favicon. </p>



<span></span>



<figure><div>
https://twitter.com/LeaVerou/status/1241619866475474946
</div></figure><p>Here's the one-liner in use:</p>



<pre rel="HTML"><code markup="tt">&lt;link rel="icon" href="data:image/svg+xml,&lt;svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22&gt;&lt;text y=%22.9em%22 font-size=%2290%22&gt;&#127919;&lt;/text&gt;&lt;/svg&gt;"&gt;</code></pre>



<h3>Demo Project</h3>



<p>I made <a href="https://codepen.io/chriscoyier/project/editor/ZeWQWJ" rel="noopener noreferrer" target="_blank">a quick little demo project</a> so you can see it at work. See <a href="https://000458870.codepen.website/" rel="noopener noreferrer" target="_blank">the deployed project</a> to actually see the favicons. That works in Firefox and Chrome. Safari only does those "mask" style icons in SVG so this doesn't work there. Maybe it could though? I dunno I'll let you try it.</p>



<p>Here's a video in case you just wanna see it.</p>



<figure><video controls src="https://res.cloudinary.com/css-tricks/video/upload/f_auto,q_auto,vc_auto/v1585074956/emojis_bxshdw.mp4"></video></figure><h3>Related Concepts</h3>



<ul><li>Ada Rose Cannon <a href="https://glitch.com/edit/#!/favicon-badge?path=script.js:1:14" rel="noopener noreferrer" target="_blank">added a badge</a> that can increment.</li><li>Taylor Hunt dropped some code on how he uses the <a href="https://gist.github.com/tigt/86b25ce970e46370dd61339380e2d6bd" rel="noopener noreferrer" target="_blank">current Git branch name</a> to create an SVG favicon (related to the <a href="https://css-tricks.com/different-favicon-for-development/" rel="noopener noreferrer" target="_blank">"different favicon for development"</a> idea)  </li><li>You could duck a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme" rel="noopener noreferrer" target="_blank"><code>prefers-color-scheme</code> media query</a> in the SVG if you wanted to do something special for dark mode (although emojis generally work well on any background)</li></ul><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/emojis-as-favicons/" target="_blank">Emojis as Favicons</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>]]></content>
	<updated>2020-03-24T18:43:23+00:00</updated>
	<author><name>Chris Coyier</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-03-24T18:43:23+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>

	<category term="favicon"/>

	<category term="svg"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-23:/399363</id>
	<link href="https://www.androidpolice.com/2020/03/23/sesame-street-ebooks-are-free-on-amazon-google-play-and-other-platforms/" rel="alternate" type="text/html"/>
	<title type="html">Sesame Street eBooks are free on Amazon, Google Play, and other platforms</title>
	<summary type="html"><![CDATA[<p>Can you tell me how to get to Sesame Street?

Many companies and organizations are working to make m...</p>]]></summary>
	<content type="html"><![CDATA[<p>Can you tell me how to get to Sesame Street?
</p><p><a href="https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street.jpg" rel="noopener noreferrer" target="_blank"><img src="https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street-728x410.jpg" alt="" srcset="https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street-728x410.jpg 728w, https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street-329x185.jpg 329w, https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street-668x376.jpg 668w, https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street-161x91.jpg 161w, https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street-217x122.jpg 217w, https://www.androidpolice.com/wp-content/uploads/2020/03/sesame-street.jpg 1920w" sizes="(max-width: 728px) 100vw, 728px" referrerpolicy="no-referrer"></a></p>
<p>Many companies and organizations are working to make movies, TV shows, books, and other entertainment free (or cheaper, at least) for folks stuck at home during the coronavirus outbreak. <a href="https://www.androidpolice.com/2020/03/19/sling-is-offering-a-bunch-of-tv-and-movies-for-free-right-now-no-signup-required/" rel="noopener noreferrer" target="_blank">Sling TV has free movies and TV shows</a>, <a href="https://www.androidpolice.com/2020/03/17/24-hour-fitness-free-app-premium-features/" rel="noopener noreferrer" target="_blank">24 Hour Fitness made some of its premium features free for everyone</a>, and now the makers of Sesame Street have made over 100 eBooks free on various platforms.<span></span></p>
<p><a href="https://www.sesameworkshop.org/press-room/press-releases/sesame-workshop-launches-caring-each-other-initiative-help-parents-and" rel="noopener noreferrer" target="_blank">Sesame Workshop announced yesterday</a> that "over 110" free Sesame Street eBooks are now available on all major platforms, including Amazon Kindle, Apple Books, Barnes &amp; Noble Nook, Google Play, and Kobo.</p><div></div> <a href="https://www.androidpolice.com/2020/03/23/sesame-street-ebooks-are-free-on-amazon-google-play-and-other-platforms/" rel="noopener noreferrer" target="_blank">Read More</a><p><a href="https://www.androidpolice.com/2020/03/23/sesame-street-ebooks-are-free-on-amazon-google-play-and-other-platforms/" rel="noopener noreferrer" target="_blank">Sesame Street eBooks are free on Amazon, Google Play, and other platforms</a> was written by the awesome team at <a href="http://www.androidpolice.com" rel="noopener noreferrer" target="_blank">Android Police</a>.</p>]]></content>
	<updated>2020-03-23T17:15:42+00:00</updated>
	<author><name>Corbin Davenport</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2020-03-23T17:15:42+00:00</updated>
		<title>Published articles</title></source>

	<category term="amazon"/>

	<category term="apple books"/>

	<category term="book"/>

	<category term="books"/>

	<category term="coronavirus"/>

	<category term="deals"/>

	<category term="google play"/>

	<category term="kindle"/>

	<category term="kobo"/>

	<category term="news"/>

	<category term="nook"/>

	<category term="series_covid_offers"/>

	<category term="sesame street"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-17:/398992</id>
	<link href="http://feedproxy.google.com/~r/blogspot/amDG/~3/hYKUsp8trCE/new-properties-virtual-or-canceled-events.html" rel="alternate" type="text/html"/>
	<title type="html">New properties for virtual, postponed, and canceled events</title>
	<summary type="html"><![CDATA[<p>In the current environment and status of COVID-19 around the world, many events are being canceled, ...</p>]]></summary>
	<content type="html"><![CDATA[<p>In the current environment and status of COVID-19 around the world, many events are being canceled, postponed, or moved to an online-only format. Google wants to show users the latest, most accurate information about your events in this fast-changing environment, and so we've added some new, optional properties to our <a href="https://developers.google.com/search/docs/data-types/event" rel="noopener noreferrer" target="_blank">developer documentation</a> to help. These properties apply to all regions and languages. This is one part of our overall efforts in <a href="http://blog.schema.org/2020/03/schema-for-coronavirus-special.html" rel="noopener noreferrer" target="_blank">schema updates</a>&nbsp;to support publishers and users. Here are some important tips on keeping Google up to date on your events. <br></p><h3>Update the status of the event</h3>The schema.org <span>eventStatus</span> property sets the status of the event, particularly when the event has been canceled, postponed, or rescheduled. This information is helpful because it allows Google to show users the current status of an event, instead of dropping the event from the event search experience altogether. <br><ul><li><b>If the event has been canceled</b>: Set the <a href="https://developers.google.com/search/docs/data-types/event#eventstatus" rel="noopener noreferrer" target="_blank"><span>eventStatus</span></a> property to <a href="https://schema.org/EventCancelled" rel="noopener noreferrer" target="_blank"><span>EventCancelled</span></a> and keep the original date in the <a href="https://developers.google.com/search/docs/data-types/event#startdate" rel="noopener noreferrer" target="_blank"><span>startDate</span></a> of the event.</li><li><b>If the event has been postponed (but the date isn't known yet)</b>: Keep the original date in the <a href="https://developers.google.com/search/docs/data-types/event#startdate" rel="noopener noreferrer" target="_blank"><span>startDate</span></a> of the event until you know when the event will take place and update the <a href="https://developers.google.com/search/docs/data-types/event#eventstatus" rel="noopener noreferrer" target="_blank"><span>eventStatus</span></a> to <a href="https://schema.org/EventPostponed" rel="noopener noreferrer" target="_blank"><span>EventPostponed</span></a>. The <a href="https://developers.google.com/search/docs/data-types/event#startdate" rel="noopener noreferrer" target="_blank"><span>startDate</span></a> property is required to help identify the unique event, and we need the date original <a href="https://developers.google.com/search/docs/data-types/event#startdate" rel="noopener noreferrer" target="_blank"><span>startDate</span></a> until you know the new date. Once you know the new date information, change the <span>eventStatus</span> to <a href="https://schema.org/EventRescheduled" rel="noopener noreferrer" target="_blank"><span>EventRescheduled</span></a> and update the <a href="https://developers.google.com/search/docs/data-types/event#startdate" rel="noopener noreferrer" target="_blank"><span>startDate</span></a> and <a href="https://developers.google.com/search/docs/data-types/event#enddate" rel="noopener noreferrer" target="_blank"><span>endDate</span></a> with the new date information.</li><li><b>If the event has been rescheduled to a later date</b>: Update the <a href="https://developers.google.com/search/docs/data-types/event#startdate" rel="noopener noreferrer" target="_blank"><span>startDate</span></a> and <a href="https://developers.google.com/search/docs/data-types/event#enddate" rel="noopener noreferrer" target="_blank"><span>endDate</span></a> with the relevant new dates. Optionally, you can also mark the <a href="https://developers.google.com/search/docs/data-types/event#eventstatus" rel="noopener noreferrer" target="_blank"><span>eventStatus</span></a> field as <a href="https://schema.org/EventRescheduled" rel="noopener noreferrer" target="_blank"><span>EventRescheduled</span></a> and add the <a href="https://developers.google.com/search/docs/data-types/event#previous-start-date" rel="noopener noreferrer" target="_blank"><span>previousStartDate</span></a>.</li><li><b>If the event has moved from in-person to online-only</b>: Optionally update the <a href="https://developers.google.com/search/docs/data-types/event#eventstatus" rel="noopener noreferrer" target="_blank"><span>eventStatus</span></a> field to indicate the change with <span>EventMovedOnline</span>.&nbsp;</li></ul><br>For more information on how to implement the <span>eventStatus</span> property, refer to the <a href="https://developers.google.com/search/docs/data-types/event#eventstatus" rel="noopener noreferrer" target="_blank">developer documentation</a>. <br><h3>Mark events as online only</h3>More events are shifting to online only, and we're actively working on a way to show this information to people on Google Search. If your event is happening only online, make sure to use the following properties: <br><ul><li>Set the location to the <a href="https://developers.google.com/search/docs/data-types/event#virtual-location" rel="noopener noreferrer" target="_blank"><span>VirtualLocation</span></a> type.</li><li>Set the <a href="https://developers.google.com/search/docs/data-types/event#event-attendance-mode" rel="noopener noreferrer" target="_blank"><span>eventAttendanceMode</span></a> property to <a href="https://schema.org/OnlineEventAttendanceMode" rel="noopener noreferrer" target="_blank"><span>OnlineEventAttendanceMode</span></a>.</li></ul><br>For more information on how to implement the <span>VirtualLocation</span> type, refer to the <a href="https://developers.google.com/search/docs/data-types/event#virtual-location" rel="noopener noreferrer" target="_blank">developer documentation</a>. <br><b>Note</b>: You can start using <span>VirtualLocation</span> and <span>eventAttendanceMode</span> even though they are still under development on Schema.org.  <br><h3>Update Google when your event changes</h3><br>After you make changes to your markup, make sure you <a href="https://developers.google.com/search/docs/guides/submit-URLs" rel="noopener noreferrer" target="_blank">update Google</a>. We recommend that you <a href="https://www.youtube.com/watch?v=y0TPINzAVf0" rel="noopener noreferrer" target="_blank">make your sitemap available automatically through your server</a>. This is the best way to make sure that your new and updated content is highlighted to search engines as quickly as possible. <br>If you have any questions, let us know through the <a href="https://support.google.com/webmasters/community" rel="noopener noreferrer" target="_blank">Webmasters forum</a> or on <a href="https://twitter.com/googlewmc" rel="noopener noreferrer" target="_blank">Twitter</a>. <br>Posted by Emily Fifer, Event Search Product Manager<div>
<a href="http://feeds.feedburner.com/~ff/blogspot/amDG?a=hYKUsp8trCE:KGII1tP96dE:yIl2AUoC8zA" rel="noopener noreferrer" target="_blank"><img src="http://feeds.feedburner.com/~ff/blogspot/amDG?d=yIl2AUoC8zA" border="0" referrerpolicy="no-referrer"></a>
</div><img src="http://feeds.feedburner.com/~r/blogspot/amDG/~4/hYKUsp8trCE" alt="" referrerpolicy="no-referrer">]]></content>
	<updated>2020-03-17T23:07:17+00:00</updated>
	<author><name>Google Webmaster Central</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2020-03-17T23:07:17+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-13:/398706</id>
	<link href="https://about.gitlab.com/blog/2020/03/13/partial-clone-for-massive-repositories/" rel="alternate" type="text/html"/>
	<title type="html">How Git Partial Clone lets you fetch only the large file you need</title>
	<summary type="html"><![CDATA[<p>The Git project began nearly 15 years ago, on April 7,
2005, and is now the
version control system o...</p>]]></summary>
	<content type="html"><![CDATA[<p>The Git project began nearly 15 years ago, on <a href="https://marc.info/?l=linux-kernel&amp;m=111288700902396" rel="noopener noreferrer" target="_blank">April 7,
2005</a>, and is now the
version control system of choice. Yet, there are certain types of projects that
often do not use Git, particularly projects that have many large binary files,
such as video games. One reason projects with large binary files don't use Git
is because, when a Git repository is cloned, Git will download every version of
every file in the repository. For most use cases, downloading this history is a
useful feature, but it slows cloning and fetching for projects with large binary
files, assuming the project even fits on your computer.</p>

<p>Partial Clone is a new feature of Git that replaces <a href="https://git-lfs.github.com/" rel="noopener noreferrer" target="_blank">Git
LFS</a> and makes working with very large repositories
better by teaching Git how to work without downloading every file. Partial Clone
has been
<a href="https://public-inbox.org/git/xmqqeg4o27zw.fsf@gitster.mtv.corp.google.com/" rel="noopener noreferrer" target="_blank">years</a>
in the making, with code contributions from GitLab, GitHub, Microsoft and
Google. Today it is experimentally available in Git and GitLab, and can be
enabled by administrators
(<a href="https://docs.gitlab.com/ee/topics/git/partial_clone.html" rel="noopener noreferrer" target="_blank">docs</a>).</p>

<p>Partial Clone speeds up fetching and cloning because less data is
transferred, and reduces disk usage on your local computer. For example, cloning
<a href="https://gitlab.com/gitlab-com/www-gitlab-com" rel="noopener noreferrer" target="_blank"><code>gitlab-com/www-gitlab-com</code></a>
using Partial Clone (<code>--filter=blob:none</code>) is at least 50% faster, and transfers
70% less data.</p>

<p>Note: Partial Clone is one specific performance optimization for very large
repositories. <a href="https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/" rel="noopener noreferrer" target="_blank">Sparse
Checkout</a>
is a related optimization that is particularly focused on repositories with
tremendously large numbers of files and revisions such as
<a href="https://devblogs.microsoft.com/bharry/the-largest-git-repo-on-the-planet/" rel="noopener noreferrer" target="_blank">Windows</a>
code base.</p>

<h2>A brief history of large files</h2>

<p>"What about Git LFS?" you may ask. Doesn't LFS stand for "large file storage"?</p>

<p>Previously, extra tools were required to store large files in Git. In 2010,
<a href="https://git-annex.branchable.com/" rel="noopener noreferrer" target="_blank">git-annex</a> was released, and five years
later in 2015, <a href="https://git-lfs.github.com/" rel="noopener noreferrer" target="_blank">Git LFS</a> was released. Both
git-annex and Git LFS added large file support to Git in a similar way: Instead
of storing a large file in Git, store a pointer file that links to the large
file. Then, when someone needs a large file, they can download it on-demand
using the pointer.</p>

<p>The criticism of this approach is that there are now two places to store files,
in Git or in Git LFS. Which means that everyone must remember that big files need
to go in Git LFS to keep the repository small and fast. There are downsides to
this approach. Besides being susceptible to human error, the pointer encodes
decisions based on bandwidth and file type into the structure of the repository
that influence all the people using the repository. Our assumptions about
bandwidth and storage are likely to change over time, and vary by the location,
but decisions encoded in the repository are not flexible. Administrators and
developers alike benefit from flexibility in where to store large files, and
which files to download.</p>

<p>Partial Clone solves these problems by removing the need for two classes of
storage, and special pointers. Let's walk through an example to understand how.</p>

<h2>Getting started with Partial Clone</h2>

<p>Let's continue to use <code>gitlab-com/www-gitlab-com</code> as an example project, since
it has quite a lot of images. For a larger repository, like a video game with
detailed textures and models, the benefits will be even more significant.</p>

<p>Instead of a vanilla <code>git clone</code>, we will include a filter spec which controls
what is excluded when fetching data. In this situation, we just want to exclude
large binary files. I've included <code>--no-checkout</code> so we can more clearly observe
what is happening.</p>

<div><pre><code>git clone <span>--filter</span><span>=</span>blob:none <span>--no-checkout</span> git@gitlab.com/gitlab-com/www-gitlab-com.git
<span># Cloning into 'www-gitlab-com'...</span>
<span># remote: Enumerating objects: 624541, done.</span>
<span># remote: Counting objects: 100% (624541/624541), done.</span>
<span># remote: Compressing objects: 100% (151886/151886), done.</span>
<span># remote: Total 624541 (delta 432983), reused 622339 (delta 430843), pack-reused 0</span>
<span># Receiving objects: 100% (624541/624541), 74.61 MiB | 8.14 MiB/s, done.</span>
<span># Resolving deltas: 100% (432983/432983), done.</span>

</code></pre></div>
<p>Above we explicitly told Git not to checkout the default branch. Normally
<code>checkout</code> doesn't require fetching any data from the server, because we have
everything locally. When using Partial Clone, since we are deliberately not
downloaded everything, Git will need to fetch any missing files when doing a
checkout.</p>

<div><pre><code>git checkout master
<span># remote: Enumerating objects: 12080, done.</span>
<span># remote: Counting objects: 100% (12080/12080), done.</span>
<span># remote: Compressing objects: 100% (11640/11640), done.</span>
<span># remote: Total 12080 (delta 442), reused 9773 (delta 409), pack-reused 0</span>
<span># Receiving objects: 100% (12080/12080), 1.10 GiB | 8.49 MiB/s, done.</span>
<span># Resolving deltas: 100% (442/442), done.</span>
<span># Updating files: 100% (12342/12342), done.</span>
<span># Filtering content: 100% (3/3), 131.24 MiB | 4.73 MiB/s, done.</span>
</code></pre></div>
<p>If we checkout a different branch or commit, we'll need to download more missing
files.</p>

<div><pre><code>git checkout 92d1f39b60f957d0bc3c5621bb3e17a3984bdf72
<span># remote: Enumerating objects: 1968, done.</span>
<span># remote: Counting objects: 100% (1968/1968), done.</span>
<span># remote: Compressing objects: 100% (1953/1953), done.</span>
<span># remote: Total 1968 (delta 23), reused 1623 (delta 15), pack-reused 0</span>
<span># Receiving objects: 100% (1968/1968), 327.44 MiB | 8.83 MiB/s, done.</span>
<span># Resolving deltas: 100% (23/23), done.</span>
<span># Updating files: 100% (2255/2255), done.</span>
<span># Note: switching to '92d1f39b60f957d0bc3c5621bb3e17a3984bdf72'.</span>
</code></pre></div>
<p>Git remembers the filter spec we provided when cloning the repository so that
fetching updates will also exclude large files until we need them.</p>

<div><pre><code>git config remote.origin.promisor
<span># true</span>

git config remote.origin.partialclonefilter
<span># blob:none</span>
</code></pre></div>
<p>When committing changes, you simply commit binary files like you would any other
file. There is no extra tool to install or configure, no need to treat big files
differently to small files.</p>

<h2>Network and Storage</h2>

<p>If you are already using <a href="https://git-lfs.github.com/" rel="noopener noreferrer" target="_blank">Git LFS</a> today, you might
be aware that large files are stored and transferred differently to regular Git
objects. On GitLab.com, Git LFS objects are stored in object storage (like AWS
S3) rather than fast attached storage (like SSD), and transferred over HTTP even
when using SSH for regular Git objects. Using object storage has the advantage
of reducing storage costs for large binary files, while using simpler HTTP
requests for large downloads allows the possibility of resumable and parallel
downloads.</p>

<p>Partial Clone
<a href="https://public-inbox.org/git/20190625134039.21707-1-chriscool@tuxfamily.org/" rel="noopener noreferrer" target="_blank">already</a>
supports more than one remote, and work is underway to allow large files to be
stored in a different location such as object storage. Unlike Git LFS, however,
the repository or instance administrator will be able to choose which objects
should be stored where, and change this configuration over time if needed.</p>

<p>Follow the epic for <a href="https://gitlab.com/groups/gitlab-org/-/epics/1487" rel="noopener noreferrer" target="_blank">improved large file
storage</a> to learn more and
follow our progress.</p>

<h2>Performance</h2>

<p>When fetching new objects from the Git server using a <a href="https://github.com/git/git/blob/v2.25.0/Documentation/rev-list-options.txt#L735" rel="noopener noreferrer" target="_blank">filter
spec</a>
 to exclude objects from the response, Git will check each object and exclude
 any that match the filter spec. In <a href="https://raw.githubusercontent.com/git/git/master/Documentation/RelNotes/2.25.0.txt" rel="noopener noreferrer" target="_blank">Git
 2.25</a>,
 the most recent version, filtering has not been optimized for performance.</p>

<p><a href="https://github.com/peff/" rel="noopener noreferrer" target="_blank">Jeff King (Peff)</a> (GitHub) recently
<a href="https://public-inbox.org/git/20200214182147.GA654525@coredump.intra.peff.net/" rel="noopener noreferrer" target="_blank">contributed</a>
performance improvements for blob size filtering, which will likely be included
in <a href="https://gitlab.com/gitlab-org/gitaly/issues/2497" rel="noopener noreferrer" target="_blank">Git 2.26</a>, and our plan is
to include it in GitLab 12.10 release.</p>

<p>Optimizing the sparse filter spec option (<code>--filter:sparse</code>), which filters
based on file path is more complex because blobs, which contain the file
content, do not include file path information. The directory structure of a
repository is stored in tree objects.</p>

<p>Follow the epic for <a href="https://gitlab.com/groups/gitlab-org/-/epics/1671" rel="noopener noreferrer" target="_blank">partial clone performance
improvements</a> to learn more
and follow our progress.</p>

<h2>Usability</h2>

<p>One of the drawbacks of Git LFS was that it required installing an additional
tool. In comparison, Partial Clone does not require any additional tools.
However, it does require learning new options and configurations, such as to
clone using the <code>--filter</code> option.</p>

<p>We want to make it easy for people get their work done, who simply desire Git to
just work. They shouldn't need to work out which is the optimal blob size filter
spec for a project? Or what even is a filter spec?  While Partial Clone remains
experimental, we haven't made any changes to the GitLab interface to highlight
Partial Clone, but we are investigating this and welcome your feedback. Please
join the conversation on this
<a href="https://gitlab.com/gitlab-org/gitlab/issues/207744" rel="noopener noreferrer" target="_blank">issue</a>.</p>

<h2>File locking and tool integrations</h2>

<p>Any conversation of large binary files, particularly in regards to video
games is incomplete without discussing file locking and tooling integrations.</p>

<p>Unlike plain text source code, resolving conflicts between different versions of
a binary file is often impossible. To prevent conflicts in binary file editing,
an exclusive file lock is used, meaning only one person at a time can edit a
file, regardless of branches. If conflicts can't be resolved, allowing multiple
versions of a file to be created in parallel on different branches is a bug, not
a feature. GitLab already has basic file locking support, but it is really only
useful for plain text because it only applies to the default branch, and is not
integrated with any local tools.</p>

<p>Local tooling integrations are important for binary asset workflows, to
automatically propagate file locks to the local development environment, and to
allow artists to work on assets without needing to use Git from the command
line. Propagating file locks quickly to local development environments is also
important because it prevents work from being wasted before it even happens.</p>

<p>Follow the <a href="https://gitlab.com/groups/gitlab-org/-/epics/1488" rel="noopener noreferrer" target="_blank">file locking</a> and
<a href="https://gitlab.com/groups/gitlab-org/-/epics/2704" rel="noopener noreferrer" target="_blank">integrations</a> epics for more
information about what we're working on.</p>

<h2>Conclusion</h2>

<p>Large files are necessary for many projects, and Git will soon support this
natively, without the need for extra tools. Although Partial Clone is still an
experimental feature, we are making improvements with every release and the
feature is now ready for testing.</p>

<p>Thank you to the Git community for your work over the past years on improving
support for enormous repositories. Particularly, thank you to <a href="https://github.com/peff/" rel="noopener noreferrer" target="_blank">Jeff
King</a> (GitHub) and <a href="https://about.gitlab.com/company/team/#chriscool" rel="noopener noreferrer" target="_blank">Christian
Couder</a> (senior backend
engineer on Gitaly at GitLab) for your early experimentation with partial clone,
Jonathan Tan (Google) and <a href="https://github.com/jeffhostetler" rel="noopener noreferrer" target="_blank">Jeff Hostetler</a>
(Microsoft) for contributing the <a href="https://public-inbox.org/git/cover.1506714999.git.jonathantanmy@google.com/" rel="noopener noreferrer" target="_blank">first
implementation</a>
of Partial Clone and promisor remotes, and the many others who've also
contributed.</p>

<p>If you are already using partial clone, or would like to help us test partial
clone on a large project, please get in touch with me, <a href="https://about.gitlab.com/company/team/#jramsay" rel="noopener noreferrer" target="_blank">James
Ramsay</a> (group manager, product
for Create at GitLab), <a href="https://about.gitlab.com/company/team/#jordi_mon" rel="noopener noreferrer" target="_blank">Jordi
Mon</a> (senior product marketing
manager for Dev at GitLab), or your account manager.</p>

<p>Cover image by <a href="https://unsplash.com/@simonlerouge" rel="noopener noreferrer" target="_blank">Simon Boxus</a> on
<a href="https://unsplash.com/photos/4ftI4lCcByM" rel="noopener noreferrer" target="_blank">Unsplash</a></p>
<img src="https://about.gitlab.com/images/blogimages/partial-clone-for-massive-repositories.jpg" referrerpolicy="no-referrer">]]></content>
	<updated>2020-03-13T00:00:00+00:00</updated>
	<author><name>James Ramsay</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2020-03-13T00:00:00+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-11:/398562</id>
	<link href="http://feedproxy.google.com/~r/LDSNewsRoomTop15/~3/8xqmUB7Bt7U/covid-19-mtc-adjustment" rel="alternate" type="text/html"/>
	<title type="html">COVID-19’s Impact on Missionary Training Centers</title>
	<summary type="html"><![CDATA[<p>Salt Lake City | Wednesday, 11 March 2020 | The First Presidency of The Church of Jesus Christ of La...</p>]]></summary>
	<content type="html"><![CDATA[<p>Salt Lake City | Wednesday, 11 March 2020 | </p><p>The First Presidency of The Church of Jesus Christ of Latter-day Saints sent the following letter Wednesday to members of the Church worldwide. The letter announces a significant change to service in missionary training centers in Provo, Utah, and Preston, England.</p><img src="https://tt-rss.goodevilgenius.org/media/960x540/ProvoMTC5799-7-2017-resized-(1).jpg" referrerpolicy="no-referrer">]]></content>
	<updated>2020-03-11T00:00:00+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://newsroom.churchofjesuschrist.org/</id>
		<link rel="self" href="https://newsroom.churchofjesuschrist.org/"/>
		<updated>2020-03-11T00:00:00+00:00</updated>
		<title>LDS Newsroom RSS Feed</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-11:/398561</id>
	<link href="http://feedproxy.google.com/~r/LDSNewsRoomTop15/~3/J0s3a4YEB5E/covid-19-impact-large-gatherings-latter-day-saints" rel="alternate" type="text/html"/>
	<title type="html">A Letter about COVID-19’s Effect on Large Gatherings of Saints</title>
	<summary type="html"><![CDATA[<p>Salt Lake City | Wednesday, 11 March 2020 | The First Presidency of The Church of Jesus Christ of La...</p>]]></summary>
	<content type="html"><![CDATA[<p>Salt Lake City | Wednesday, 11 March 2020 | </p><p>The First Presidency of The Church of Jesus Christ of Latter-day Saints sent the following letter Wednesday to members of the Church worldwide. The letter details a notable restriction on large gatherings of Latter-day Saints.</p><img src="https://tt-rss.goodevilgenius.org/media/960x540/cedar-city.jpg" referrerpolicy="no-referrer"><div>
<a href="http://feeds.feedburner.com/~ff/LDSNewsRoomTop15?a=J0s3a4YEB5E:3RlQ_t72vW8:yIl2AUoC8zA" rel="noopener noreferrer" target="_blank"><img src="http://feeds.feedburner.com/~ff/LDSNewsRoomTop15?d=yIl2AUoC8zA" border="0" referrerpolicy="no-referrer"></a>
</div><img src="http://feeds.feedburner.com/~r/LDSNewsRoomTop15/~4/J0s3a4YEB5E" alt="" referrerpolicy="no-referrer">]]></content>
	<updated>2020-03-11T00:00:00+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://newsroom.churchofjesuschrist.org/</id>
		<link rel="self" href="https://newsroom.churchofjesuschrist.org/"/>
		<updated>2020-03-11T00:00:00+00:00</updated>
		<title>LDS Newsroom RSS Feed</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-11:/398560</id>
	<link href="http://feedproxy.google.com/~r/LDSNewsRoomTop15/~3/PTlX5-SkYL4/april-2020-general-conference-format-change" rel="alternate" type="text/html"/>
	<title type="html">How COVID-19 Will Impact the April 2020 General Conference</title>
	<summary type="html"><![CDATA[<p>Salt Lake City | Wednesday, 11 March 2020 | The First Presidency of The Church of Jesus Christ of La...</p>]]></summary>
	<content type="html"><![CDATA[<p>Salt Lake City | Wednesday, 11 March 2020 | </p><p>The First Presidency of The Church of Jesus Christ of Latter-day Saints sent the following letter today to members of the Church worldwide.&nbsp;</p><img src="https://tt-rss.goodevilgenius.org/media/960x540/333286bcfe8d0f4cc88ad3443809c12893931581.jpeg" referrerpolicy="no-referrer"><div>
<a href="http://feeds.feedburner.com/~ff/LDSNewsRoomTop15?a=PTlX5-SkYL4:yERdSyl0l38:yIl2AUoC8zA" rel="noopener noreferrer" target="_blank"><img src="http://feeds.feedburner.com/~ff/LDSNewsRoomTop15?d=yIl2AUoC8zA" border="0" referrerpolicy="no-referrer"></a>
</div><img src="http://feeds.feedburner.com/~r/LDSNewsRoomTop15/~4/PTlX5-SkYL4" alt="" referrerpolicy="no-referrer">]]></content>
	<updated>2020-03-11T00:00:00+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://newsroom.churchofjesuschrist.org/</id>
		<link rel="self" href="https://newsroom.churchofjesuschrist.org/"/>
		<updated>2020-03-11T00:00:00+00:00</updated>
		<title>LDS Newsroom RSS Feed</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-03-02:/397961</id>
	<link href="https://css-tricks.com/selectors-explained/" rel="alternate" type="text/html"/>
	<title type="html">Selectors Explained</title>
	<summary type="html"><![CDATA[<p>Have you ever found yourself either writing a CSS selector that winds up looking confusing as heck,...</p>]]></summary>
	<content type="html"><![CDATA[<p>Have you ever found yourself either writing a CSS selector that winds up looking confusing as heck, or seen one while reading through someone's code? That happened to me the other day.</p>



<p>Here's what I wrote:</p>



<pre rel="CSS"><code markup="tt">.site-footer__nav a:hover &gt; svg ellipse:first-child { }</code></pre>



<p>At the end of it, I honestly couldn't even explain what it does to myself. LOL, that probably means there was a better way to write it.</p>



<p>But Hugo Giraudel has this handy new tool that will explain any selector you throw at it.</p>



<span></span>



<p>Here's how it explained mine:</p>



<blockquote><p>An <code>&lt;ellipse&gt;</code> element provided it is the first child of its parent somewhere<br> &hellip; within a <code>&lt;svg&gt;</code> element <br> &hellip; itself directly within an <code>&lt;a&gt;</code>  element provided it is hovered <br> &hellip; itself somewhere<br> &hellip; within an element with class <code>site-footer__nav</code>.</p></blockquote>



<p>Bravo! It even spits out the specificity of the selector to boot. &#128079;</p>
<p><a href="https://hugogiraudel.github.io/selectors-explained/" title="Direct link to featured article" rel="noopener noreferrer" target="_blank">Direct Link to Article</a> &mdash; <a href="https://css-tricks.com/selectors-explained/" rel="noopener noreferrer" target="_blank">Permalink</a></p><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/selectors-explained/" target="_blank">Selectors Explained</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>]]></content>
	<updated>2020-03-02T14:54:40+00:00</updated>
	<author><name>Geoff Graham</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-03-02T14:54:40+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="link"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-02-21:/397342</id>
	<link href="https://kottke.org/20/02/a-dj-mixes-songs-that-sound-the-same" rel="alternate" type="text/html"/>
	<title type="html">A DJ Mixes Songs That Sound The Same</title>
	<summary type="html"><![CDATA[<p>From DJ Mike 2600, a YouTube series called Songs That Sound The Same.

My hit series of DJ ...</p>]]></summary>
	<content type="html"><![CDATA[<p>From DJ <a href="https://soundcloud.com/mike2600" rel="noopener noreferrer" target="_blank">Mike 2600</a>, a YouTube series called <a href="https://www.youtube.com/playlist?list=PLNfE79HMZCv29h_-Cfa_zrPUTLVMfCh7x" rel="noopener noreferrer" target="_blank">Songs That Sound The Same</a>.</p>

<blockquote><p>My hit series of DJ videos exploring pairs of songs that aren&rsquo;t direct covers or rip-offs, but have similar melodies, riffs, or chord progressions and just fit together nicely.</p></blockquote>

<p>Each video is about a minute long and features him playfully mixing two or more songs together that sound very similar. Here&rsquo;s one of the early episodes, featuring Hot Fun in the Summertime by Sly &amp; the Family Stone and Misunderstanding by Genesis:</p>

<p></p>

<p>T.I.&rsquo;s Whatever You Like and Zombie by The Cranberries:</p>

<p></p>

<p>Whomst among us wouldn&rsquo;t go nuts if the DJ laid this down at the club &mdash; M83&rsquo;s Midnight City &amp; Rihanna&rsquo;s Diamonds:</p>

<p></p>

<p>And this one made me LOL &mdash; Draggin&rsquo; the Line by Tommy James mixes really well with the Baby Back Ribs song from the Chili&rsquo;s commercial:</p>

<p></p>

<p>What a great combination of creativity and craft. Watching stuff like this always makes this non-musical person want to make some music. (via <a href="https://twitter.com/hoodinternet" rel="noopener noreferrer" target="_blank">@hoodinternet</a>)</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/Mike%202600" rel="noopener noreferrer" target="_blank">Mike 2600</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/music" rel="noopener noreferrer" target="_blank">music</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/remix" rel="noopener noreferrer" target="_blank">remix</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/video" rel="noopener noreferrer" target="_blank">video</a>]]></content>
	<updated>2020-02-21T14:43:43+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2020-02-21T14:43:43+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-01-17:/394969</id>
	<link href="https://css-tricks.com/eleventy-love/" rel="alternate" type="text/html"/>
	<title type="html">Eleventy Love</title>
	<summary type="html"><![CDATA[<p>Been seeing a lot of Eleventy action lately. It's a smaller player in the world of static site gener...</p>]]></summary>
	<content type="html"><![CDATA[<p>Been seeing a lot of <a href="https://www.11ty.dev/" rel="noopener noreferrer" target="_blank">Eleventy</a> action lately. It's a smaller player in the world of static site generators, but I think it's got huge potential because of how simple it is, yet does about anything you'd need it to do. It's Just JavaScript&trade;.</p>
<ul><li>Jason Lengstorf and Zach Leatherman did a <i><a href="https://www.learnwithjason.dev/let-s-learn-eleventy" rel="noopener noreferrer" target="_blank">Learn with Jason</a></i> episode on it.</li>
<li>Reginald Hunt has <a href="https://rphunt.github.io/eleventy-walkthrough/" rel="noopener noreferrer" target="_blank">a big ol' guide</a> on it.</li>
<li>Phil Hawksworth has all these starters for it, like <a href="https://github.com/philhawksworth/eleventyone" rel="noopener noreferrer" target="_blank">EleventyOne</a> and <a href="https://github.com/philhawksworth/eleventail" rel="noopener noreferrer" target="_blank">ElevenTail</a>.</li>
<li>Andy Bell has <a href="https://github.com/hankchizljaw/hylia" rel="noopener noreferrer" target="_blank">Hylia</a>, another starter that's preconfigured with Netlify CMS and a bunch of other goodies.</li>
</ul><p>The post <a rel="noopener noreferrer" href="https://css-tricks.com/eleventy-love/" target="_blank">Eleventy Love</a> appeared first on <a rel="noopener noreferrer" href="https://css-tricks.com" target="_blank">CSS-Tricks</a>.</p>]]></content>
	<updated>2020-01-17T16:35:09+00:00</updated>
	<author><name>Chris Coyier</name></author>
	<source>
		<id>https://css-tricks.com</id>
		<link rel="self" href="https://css-tricks.com"/>
		<updated>2020-01-17T16:35:09+00:00</updated>
		<title>CSS-Tricks</title></source>

	<category term="article"/>

	<category term="eleventy"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2020-01-07:/394132</id>
	<link href="http://feedproxy.google.com/~r/LDSNewsRoomTop15/~3/eFHrc9sAdm4/joseph-smith-papers-first-vision-podcast" rel="alternate" type="text/html"/>
	<title type="html">New Podcast Series Looks in Depth at Joseph Smith’s First Vision of Deity</title>
	<summary type="html"><![CDATA[<p>Salt Lake City | Monday, 06 January 2020 | A new six-episode podcast series from the editors of The ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Salt Lake City | Monday, 06 January 2020 | </p><p>A new six-episode podcast series from the editors of <em>The Joseph Smith Papers</em> analyzes Joseph Smith&rsquo;s first encounter with Deity (known by The Church of Jesus Christ of Latter-day Saints as the First Vision) &mdash; an event that occurred 200 years ago this spring.</p><img src="https://tt-rss.goodevilgenius.org/media/500x500/1a55d9bd6d2387bce63f15b61a83f3ffe2e4ad27.jpeg" referrerpolicy="no-referrer">]]></content>
	<updated>2020-01-06T00:00:00+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://newsroom.churchofjesuschrist.org/</id>
		<link rel="self" href="https://newsroom.churchofjesuschrist.org/"/>
		<updated>2020-01-06T00:00:00+00:00</updated>
		<title>LDS Newsroom RSS Feed</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-12-14:/392524</id>
	<link href="http://feedproxy.google.com/~r/Dudeiwantthat/~3/81Nif3wxEkA/potty-piano.asp" rel="alternate" type="text/html"/>
	<title type="html">Potty Piano</title>
	<summary type="html"><![CDATA[<p>Tinkle, tinkle little star. It's a potty party with the Potty Piano, a play on the toilet mat that a...</p>]]></summary>
	<content type="html"><![CDATA[<p>Tinkle, tinkle little star. It's a potty party with the Potty Piano, a play on the toilet mat that allows you to, well, play the toilet mat, and let your creative juices flow right alongside your biological ones.

Your standard bathroom buddy might...</p>]]></content>
	<updated>2019-12-14T16:49:00+00:00</updated>
	<author><name>info@dudeiwantthat.com Erin Carstens</name></author>
	<source>
		<id>http://www.dudeiwantthat.com</id>
		<link rel="self" href="http://www.dudeiwantthat.com"/>
		<updated>2019-12-14T16:49:00+00:00</updated>
		<title>DudeIWantThat.com</title></source>

	<category term="poo"/>

	<category term="bathroom"/>

	<category term="toilet"/>

	<category term="pee"/>

	<category term="piano"/>

	<category term="song"/>

	<category term="poop"/>

	<category term="potty"/>

	<category term="tune"/>


	<link rel="enclosure" 
		type="image/jpeg" 
		length="1000"
		href="http://static.dudeiwantthat.com/img/gear/novelty/potty-piano-41782.jpg"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-11-25:/390967</id>
	<link href="https://vueschool.io/articles/vuejs-tutorials/exciting-new-features-in-vue-3/" rel="alternate" type="text/html"/>
	<title type="html">Learn Which Exciting Features Vue 3 Brings to The Table - Vue School Vue.js Tutorials</title>
	<summary type="html"><![CDATA[]]></summary>
	<content type="html"><![CDATA[]]></content>
	<updated>2019-11-25T11:15:16+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-11-25T11:15:16+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-11-16:/390396</id>
	<link href="http://instantwatcher.com/title/81132624" rel="alternate" type="text/html"/>
	<title type="html">Iron Sky: The Coming Race (2019) TV-MA [Movie]</title>
	<summary type="html"><![CDATA[<p>Decades after a nuclear war, survivors on the moon revisit Earth to locate a potential power ...</p>]]></summary>
	<content type="html"><![CDATA[<p><img src="http://occ-0-2430-116.1.nflxso.net/dnm/api/v6/XsrytRUxks8BtTRf9HNlZkW2tvY/AAAABbDpx1XXVqgNt6lhufYD0-VzpTbKmBn-_fjeX2zA-5QkOK2bGTggaok9dBxm_GwD7amxDaJ8QpoRFn8yGDAZ-D4.jpg?r=327" referrerpolicy="no-referrer"> Decades after a nuclear war, survivors on the moon revisit Earth to locate a potential power source -- and find shape-shifting reptiles instead.</p>]]></content>
	<updated>2019-11-16T13:00:00+00:00</updated>
	<author><name>instantwatcher.com</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-11-16T13:00:00+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-11-16:/390399</id>
	<link href="https://opensource.com/article/19/11/pyradio" rel="alternate" type="text/html"/>
	<title type="html">PyRadio: An open source alternative for internet radio | Opensource.com</title>
	<summary type="html"><![CDATA[]]></summary>
	<content type="html"><![CDATA[]]></content>
	<updated>2019-11-16T14:16:23+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-11-16T14:16:23+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-11-04:/389612</id>
	<link href="https://www.blog.google/products/google-play/google-play-points-rewards-program-all-ways-you-play/" rel="alternate" type="text/html"/>
	<title type="html">Google Play Points: a rewards program for all the ways you Play</title>
	<summary type="html"><![CDATA[<p>Since 2012, Google Play has been your place to find and enjoy apps, games, movies, TV shows, and boo...</p>]]></summary>
	<content type="html"><![CDATA[<div><div><p>Since 2012, Google Play has been your place to find and enjoy apps, games, movies, TV shows, and books. More than 2 billion people in 190 countries use Google Play to discover blockbuster movies, apps that help you be more productive, and books that inspire imagination.</p><p>To show our appreciation, we created a new rewards program called Google Play Points that lets you earn points and rewards for the ways you already use Google Play. Over the past year, millions of people in Japan and South Korea have joined the program, and starting today, Google Play Points is launching in the United States.</p><p>It&rsquo;s free to join, and you can earn Play Points to use for special items and discounts in top games like Candy Crush Saga and Pok&eacute;mon GO, or for Google Play Credit to use on movies, books, games, and apps.</p></div></div><div><div><h3>Play your way and earn points</h3><p>With Google Play Points, you&rsquo;ll earn points on everything you buy with Google Play, including in-app items, movies, books, subscriptions and more. You can also earn Play Points by downloading featured free apps and games. Weekly points events can boost your earning rate on movies, books, and select games.&nbsp;</p><p>Google Play Points has four levels, from Bronze to Platinum. Your level depends on how many points you&rsquo;ve collected, and higher levels have perks like weekly prizes.</p></div></div><div><div><h3>Redeem your Play Points how you&rsquo;d like</h3><p>We&rsquo;re partnering with developers of some of the top apps and games on Google Play so that you can redeem points for special in-app items like characters, gems and more. You can also use Play Points for Google Play Credit and rent an award-winning movie or buy a best-selling audiobook.&nbsp;</p><p>We&rsquo;ve been inspired by the Play community&rsquo;s generosity and participation in&nbsp;<a href="https://www.blog.google/products/google-play/give-back-charitable-donations-google-play/" rel="noopener noreferrer" target="_blank">Play Store giving</a>. You can use your points to help support a great cause&mdash;or causes&mdash;of your choice from a rotating list of nonprofits, starting with Doctors Without Borders USA, Save the Children and the World Food Program USA.&nbsp;</p></div></div><div><div><div><figure><img alt="blog_enroll.png" src="https://storage.googleapis.com/gweb-uniblog-publish-prod/images/blog_enroll.max-1000x1000.png" referrerpolicy="no-referrer"></figure></div></div></div><div><div><h3>Join for free</h3>Google Play Points will be available over the next week. It&rsquo;s free to join, there is no recurring or monthly fee, and you&rsquo;ll earn three times the Play Points on everything you buy your first week. To get started, open the Play Store app on your Android device. Tap menu, then Play Points. <a href="https://play.google.com/about/playpoints/" rel="noopener noreferrer" target="_blank">Learn more</a> about Play Points--and get ready to earn points and rewards.</div></div>]]></content>
	<updated>2019-11-04T17:00:00+00:00</updated>
	<author><name>Winston Mok</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-11-04T17:00:00+00:00</updated>
		<title>Published articles</title></source>

	<category term="google play"/>


	<link rel="enclosure" 
		type="" 
		length="1"
		href="https://storage.googleapis.com/gweb-uniblog-publish-prod/images/blog_hero.max-600x600.png"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-08-19:/384840</id>
	<link href="https://kottke.org/19/08/how-all-this-could-be-different" rel="alternate" type="text/html"/>
	<title type="html">How [Points In A Circle] All This Could Be Different</title>
	<summary type="html"><![CDATA[<p>Gizmodo has a pretty cool theme this week: the Alternate Internet. Not, like, the internet ...</p>]]></summary>
	<content type="html"><![CDATA[<p>Gizmodo has a pretty cool theme this week: <a href="https://gizmodo.com/c/alternate-internet" rel="noopener noreferrer" target="_blank">the Alternate Interne</a>t. Not, like, the internet where they play Stone Temple Pilots b-sides &mdash; although, cool idea. No, like, the internet of alternate universes, different pasts and futures, where things went differently and like, Yahoo doesn&rsquo;t ruin all it touches. That&rsquo;s my kind of party.</p>

<p><img src="https://tt-rss.goodevilgenius.org/plus/misc/images/Social%20Media%20Outlines.jpg" border="0" alt="Social Media Outlines.jpg" referrerpolicy="no-referrer"></p>

<p>The story on <a href="https://gizmodo.com/why-these-social-networks-failed-so-badly-1836996164" rel="noopener noreferrer" target="_blank">alternate social media networks</a> offers a nice media archaeology of all these sites; how they were covered and explained in the moment. Check out this explanation of Friendster in SF Weekly from August 13, 2003:</p>

<p> </p><blockquote><p>&ldquo;Your induction into Friendster starts out innocently enough: You receive an e-mail invitation from a friend. It doesn&rsquo;t cost anything to join, so you give it a whirl. You answer questions about your profession, favorite books, movies, music, and other interests, then upload a digital photo of yourself.</p>

<p>Thumbnail versions of your friends&rsquo; photos appear on your profile page like a collection of trading cards. Clicking on their pictures takes you to their pages, where you can see all of their friends, and so on. Even with only a few friends, you find that &mdash; through friends of friends &mdash; you suddenly have access to a social network of thousands of people.&rdquo;</p></blockquote>

<p>It&rsquo;s a pretty common story what happened to all these sites; either they took the money, and got mismanaged, driving off whatever users they had in the first place, or they didn&rsquo;t take the money, which meant they could never stay usable enough to support all of the users they were taking on. </p>

<p>A few other common threads: a fear of porn, other lewd behavior, and Yik Yak -style harassment. In short, it&rsquo;s hard to make a social media site actually go big. You need money, a vanilla rep, ruthlessness, and more money. In the end, Facebook really was in the right place at the right time.</p>

<p>The one thing this doesn&rsquo;t do is some deep left-field speculating, like, what if Google had seen what it had in Blogger to create Facebook before Facebook? Or Yahoo had figured it out with version X of whatever asset Yahoo had? There are social networks, real or latent, bigger than the social media graveyeard &mdash; some of the pieces are still here.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/Facebook" rel="noopener noreferrer" target="_blank">Facebook</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/social%20media" rel="noopener noreferrer" target="_blank">social media</a>]]></content>
	<updated>2019-08-19T16:45:00+00:00</updated>
	<author><name>Tim Carmody</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2019-08-19T16:45:00+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-08-16:/384651</id>
	<link href="https://blog.xkcd.com/2019/08/16/serena-versus-the-drones/" rel="alternate" type="text/html"/>
	<title type="html">Serena Versus the Drones</title>
	<summary type="html"><![CDATA[<p>My new book, How To, comes out on September 3rd (preorder: Amazon, Barnes &amp; Noble, IndieBound, ...</p>]]></summary>
	<content type="html"><![CDATA[<p><em>My new book, <a href="https://xkcd.com/how-to/" rel="noopener noreferrer" target="_blank">How To</a>, comes out on September 3<sup>rd</sup> (preorder: <a href="https://www.amazon.com/How-Absurd-Scientific-Real-World-Problems/dp/0525537090/" rel="noopener noreferrer" target="_blank">Amazon</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/2/trackingcode/PRH5522E62429" rel="noopener noreferrer" target="_blank">Barnes &amp; Noble</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/6/trackingcode/penguinrandom" rel="noopener noreferrer" target="_blank">IndieBound</a>, and&nbsp;<a href="https://itunes.apple.com/us/book/how-to/id1451461524?mt=11" rel="noopener noreferrer" target="_blank">Apple Books</a>).</em></p>



<p><em>One of the most exciting things about writing </em>How To<em> was that, for a few chapters, I was able to reach out to some extremely cool people who were willing to apply their unique expertise to ridiculous tasks. Among those who generously agreed to help was <a href="https://www.serenaventures.com/" rel="noopener noreferrer" target="_blank">Serena Williams</a>. </em></p>



<p><em>Here&rsquo;s a portion of the chapter &ldquo;How to Catch a Drone&rdquo;, in which Serena helped test whether tennis serves could be an effective countermeasure against flying robots &hellip; by taking a drone out onto a court and hitting tennis balls at it until it crashed.</em></p>



<hr><h4>How to Catch a Drone</h4>



<p>A wedding-photography drone is buzzing around above you. You don&rsquo;t know what it&rsquo;s doing there and you want it to stop.</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_1.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_1.png?w=309&amp;h=256 309w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_1.png?w=618&amp;h=512 618w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_1.png?w=150&amp;h=124 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_1.png?w=300&amp;h=249 300w" sizes="(max-width: 309px) 100vw, 309px" referrerpolicy="no-referrer"></figure></div>



<p>Let&rsquo;s suppose you have a garage full of sports equipment&mdash; baseballs, tennis rackets, lawn darts, you name it. Which sport&rsquo;s projectiles would work best for hitting a drone? And who would make the best anti-drone guard? A baseball pitcher? A basketball player? A tennis player? A golfer? Someone else?</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png?w=619&amp;h=485 619w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png?w=150&amp;h=118 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png?w=300&amp;h=235 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png?w=768&amp;h=603 768w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png?w=1024&amp;h=804 1024w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png 1083w" sizes="(max-width: 619px) 100vw, 619px" referrerpolicy="no-referrer"></figure></div>



<p>There are a few factors to consider &mdash; accuracy, weight, range, and projectile size.</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png?w=717&amp;h=394 717w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png?w=150&amp;h=82 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png?w=300&amp;h=165 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png?w=768&amp;h=422 768w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png?w=1024&amp;h=562 1024w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png 1278w" sizes="(max-width: 719px) 100vw, 719px" referrerpolicy="no-referrer"></figure></div>



<p>One sport I couldn&rsquo;t find good data on was tennis. I found some studies of tennis pro accuracy, but they involved hitting targets marked on the court, rather than in the air.</p>



<p>So I reached out to Serena Williams.</p>



<p>To my pleasant surprise, she was happy to help out. Her husband, Alexis, offered a sacrificial drone, a DJI Mavic Pro 2 with a broken camera. They headed out to her practice court to see how effective the world&rsquo;s best tennis player would be at fending off a robot invasion.</p>



<p>The few studies I could find suggested tennis players would score relatively low com- pared to athletes who threw projectiles&mdash; more like kickers than pitchers. My tentative guess was that a champion player would have an accuracy ratio around 50 when serving, and take 5&ndash;7 tries to hit a drone from 40 feet. (Would a tennis ball even knock down a drone? Maybe it would just ricochet off and cause the drone to wobble! I had so many questions.)</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png?w=644&amp;h=332 644w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png?w=150&amp;h=77 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png?w=300&amp;h=155 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png?w=768&amp;h=396 768w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png?w=1024&amp;h=528 1024w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png 1148w" sizes="(max-width: 644px) 100vw, 644px" referrerpolicy="no-referrer"></figure></div>



<p>Alexis flew the drone over the net and hovered there, while Serena served from the baseline.</p>



<p>Her first serve went low. The second zipped past the drone to one side.</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_9.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_9.png?w=477&amp;h=183 477w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_9.png?w=150&amp;h=58 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_9.png?w=300&amp;h=115 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_9.png 667w" sizes="(max-width: 477px) 100vw, 477px" referrerpolicy="no-referrer"></figure></div>



<p>The third serve scored a direct hit on one of the propellers. The drone spun, momentarily seemed like it might stay in the air, then flipped over and smashed into the court. Serena started laughing as Alexis walked over to investigate the crash site, where the drone lay on the court near several propeller fragments.</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_10.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_10.png?w=359&amp;h=220 359w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_10.png?w=150&amp;h=92 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_10.png?w=300&amp;h=184 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_10.png 419w" sizes="(max-width: 359px) 100vw, 359px" referrerpolicy="no-referrer"></figure></div>



<p>I had expected a tennis pro would be able to hit the drone in five to seven tries; she got it in three.</p>



<div><figure><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png?w=723&amp;h=732 723w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png?w=148&amp;h=150 148w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png?w=296&amp;h=300 296w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png?w=768&amp;h=779 768w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png?w=1010&amp;h=1024 1010w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png 1289w" sizes="(max-width: 723px) 100vw, 723px" referrerpolicy="no-referrer"></figure></div>



<hr><p><em>For more on anti-drone strategies, sports projectile accuracy, a discussion with a robot ethicist about whether hitting a drone with a tennis ball is wrong, and many other topics, check out <strong>How To: Absurd Scientific Advice for Common Real-World Problems</strong>, available September 3<sup>rd</sup> (preorder: <a href="https://www.amazon.com/How-Absurd-Scientific-Real-World-Problems/dp/0525537090/" rel="noopener noreferrer" target="_blank">Amazon</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/2/trackingcode/PRH5522E62429" rel="noopener noreferrer" target="_blank">Barnes &amp; Noble</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/6/trackingcode/penguinrandom" rel="noopener noreferrer" target="_blank">IndieBound</a>, and&nbsp;<a href="https://itunes.apple.com/us/book/how-to/id1451461524?mt=11" rel="noopener noreferrer" target="_blank">Apple Books</a>).</em></p>]]></content>
	<updated>2019-08-16T11:16:07+00:00</updated>
	<author><name>Randall</name></author>
	<source>
		<id>http://blog.xkcd.com</id>
		<link rel="self" href="http://blog.xkcd.com"/>
		<updated>2019-08-16T11:16:07+00:00</updated>
		<title>xkcd</title></source>

	<category term="uncategorized"/>


	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://0.gravatar.com/avatar/376789f08db9745413f487c5b8c4afcb?s=96&amp;d=identicon&amp;r=G"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_1.png"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_4.png"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_5.png"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_8.png"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_9.png"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_10.png"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/drone_11.png"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-08-12:/384378</id>
	<link href="http://instantwatcher.com/title/81091977" rel="alternate" type="text/html"/>
	<title type="html">Rocko's Modern Life: Static Cling (2019) TV-Y7 [Movie]</title>
	<summary type="html"><![CDATA[<p>After 20 years in space, Rocko struggles to adjust to life in 21st century O-Town and makes i...</p>]]></summary>
	<content type="html"><![CDATA[<p><img src="https://dnm.nflximg.net/api/v6/XsrytRUxks8BtTRf9HNlZkW2tvY/AAAABUWoAI7b-kSKpPBLQhkS_Jr8guOEuijbwy4d5NMzHxLGJsjKKESw2SSX7krXRJ14p9vpnEVXJ90Zz8HLVFCeiYO4ifHl1VZnwTjFbxl5HagJ4Zqtb14aH0mK.jpg?r=977" referrerpolicy="no-referrer"> After 20 years in space, Rocko struggles to adjust to life in 21st century O-Town and makes it his mission to get his favorite show back on the air.</p>]]></content>
	<updated>2019-08-09T13:00:00+00:00</updated>
	<author><name>instantwatcher.com</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-08-09T13:00:00+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-08-02:/383879</id>
	<link href="https://blog.xkcd.com/2019/08/02/how-to-chapter-list-and-introduction/" rel="alternate" type="text/html"/>
	<title type="html">How To: Chapter list and introduction</title>
	<summary type="html"><![CDATA[<p>My new book, How To,&nbsp;comes out next month! You can preorder it on Amazon, Barnes &amp; Noble, IndieB...</p>]]></summary>
	<content type="html"><![CDATA[<p>My new book, <strong><em><a href="https://xkcd.com/how-to/" rel="noopener noreferrer" target="_blank">How To</a>,&nbsp;</em></strong>comes out next month! You can preorder it on <a href="https://www.amazon.com/How-Absurd-Scientific-Real-World-Problems/dp/0525537090/" rel="noopener noreferrer" target="_blank">Amazon</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/2/trackingcode/PRH5522E62429" rel="noopener noreferrer" target="_blank">Barnes &amp; Noble</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/6/trackingcode/penguinrandom" rel="noopener noreferrer" target="_blank">IndieBound</a>, and&nbsp;<a href="https://itunes.apple.com/us/book/how-to/id1451461524?mt=11" rel="noopener noreferrer" target="_blank">Apple Books</a>.</p>
<p><a href="https://www.amazon.com/How-Absurd-Scientific-Real-World-Problems/dp/0525537090/" rel="noopener noreferrer" target="_blank"><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-open.png?w=300" alt="blog-open" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-open.png?w=300 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-open.png?w=600 600w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-open.png?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" referrerpolicy="no-referrer"></a></p>
<p>I&rsquo;m really excited about it! Here&rsquo;s the first preview of what&rsquo;s inside:</p>
<h2>Chapter list</h2>
<ul><li>How to Jump Really High</li>
<li>How to Throw a Pool Party</li>
<li>How to Dig a Hole</li>
<li>How to Play the Piano</li>
<li>How to Make an Emergency Landing</li>
<li>How to Cross a River</li>
</ul><p><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-hole.png?w=300" alt="blog-hole" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-hole.png?w=300 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-hole.png?w=600 600w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-hole.png?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" referrerpolicy="no-referrer"></p>
<ul><li>How to Move</li>
<li>How to Keep Your House from Moving</li>
<li>How to Build a Lava Moat</li>
<li>How to Throw Things</li>
<li>How to Play Football</li>
<li>How to Predict the Weather</li>
<li>How to Play Tag</li>
<li>How to Ski</li>
</ul><p><a href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-move.png" rel="noopener noreferrer" target="_blank"><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-move.png?w=300" alt="" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-move.png?w=300 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-move.png?w=600 600w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-move.png?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" referrerpolicy="no-referrer"></a></p>
<ul><li>How to Mail a Package</li>
<li>How to Power Your House (on Earth)</li>
<li>How to Power Your House (on Mars)</li>
<li>How to Make Friends</li>
<li>How to Send a File</li>
<li>How to Charge Your Phone</li>
<li>How to Take a Selfie</li>
</ul><p><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-river.png?w=300" alt="blog-river" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-river.png?w=300 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-river.png?w=600 600w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-river.png?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" referrerpolicy="no-referrer"></p>
<ul><li>How to Catch a Drone</li>
<li>How to Tell If You&rsquo;re a Nineties Kid</li>
<li>How to Win an Election</li>
<li>How to Decorate a Tree</li>
<li>How to Get Somewhere Fast</li>
<li>How to Be On Time</li>
<li>How to Dispose of This Book</li>
</ul><p>In addition to the main chapters, the book also features short guides on topics including tornado chasing, dog walking, and highway engineering.</p>
<h2>Introduction</h2>
<p>This is a book of bad ideas.</p>
<p>At least, most of them are bad ideas. It&rsquo;s possible some good ones slipped through the cracks. If so, I apologize.</p>
<p>Some ideas that sound ridiculous turn out to be revolutionary. Smearing mold on an infected cut sounds like a terrible idea, but the discovery of penicillin showed that it could be a miracle cure. On the other hand, the world is full of disgusting stuff that you <em>could</em> smear on a wound, and most of them won&rsquo;t make it better. Not all ridiculous ideas are good. So how do we tell the good ideas from the bad? We can try them and see what happens. But sometimes, we can use math, research, and things we already know to work out what will happen if we do.</p>
<p>When NASA was planning to send its car-size <em>Curiosity</em> rover to Mars, they had to figure out how to land it gently on the surface. Previous rovers had landed using parachutes and air bags, so NASA engineers considered this approach with <em>Curiosity</em>, but the rover was too large and heavy for parachutes to slow it down enough in Mars&rsquo;s thin atmosphere. They also thought about mounting rockets on the rover to let it hover and touch down gently, but the exhaust would create dust clouds that would obscure the surface and make it hard to land safely.</p>
<p>Eventually, they came up with the idea of a <span>&ldquo;</span>sky crane<span>&rdquo;</span> &mdash;a vehicle that would hover high above the surface using rockets while lowering <em>Curiosity</em> to the ground on a long tether. This sounded like a ridiculous idea, but every other idea they could come up with was worse. The more they looked at the sky crane idea, the more plausible it seemed. So they tried it, and it worked.</p>
<p>We all start out life not knowing how to do things. If we&rsquo;re lucky, when we need to do something, we can find someone to show us how. But sometimes, we have to come up with a way to do it ourselves. This means thinking of ideas and then trying to decide whether they&rsquo;re good or not.</p>
<p>This book explores unusual approaches to common tasks, and looks at what would happen to you if you tried them. Figuring out why they would or wouldn&rsquo;t work can be fun and informative and sometimes lead you to surprising places. Maybe an idea is bad, but figuring out exactly <em>why</em> it&rsquo;s a bad idea can teach you a lot&mdash;and might help you think of a better approach.</p>
<p>And even if you already know the right way to do all these things, it can be helpful to try to look at the world through the eyes of someone who doesn&rsquo;t. After all, for anything that <span>&ldquo;</span>everyone knows<span>&rdquo; </span>by the time they reach adulthood, every day over 10,000 people in the United States alone are learning it for the first time.</p>
<p><img src="https://blogdotxkcddotcom.files.wordpress.com/2019/08/ten_thousand.png" alt="ten_thousand" srcset="https://blogdotxkcddotcom.files.wordpress.com/2019/08/ten_thousand.png 778w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/ten_thousand.png?w=150&amp;h=85 150w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/ten_thousand.png?w=300&amp;h=170 300w, https://blogdotxkcddotcom.files.wordpress.com/2019/08/ten_thousand.png?w=768&amp;h=434 768w" sizes="(max-width: 778px) 100vw, 778px" referrerpolicy="no-referrer"></p>
<p>That&rsquo;s why I don&rsquo;t like making fun of people for admitting they don&rsquo;t know something or never learned how to do something. Because if you do that, all it does is teach them not to tell you when they&rsquo;re learning something . . . and you miss out on the fun.</p>
<p>This book may not teach you how to throw a ball, how to ski, or how to move. But I hope you learn something from it. If you do, you&rsquo;re one of today&rsquo;s lucky 10,000.</p>
<p><em>Preorder:&nbsp;<a href="https://www.amazon.com/How-Absurd-Scientific-Real-World-Problems/dp/0525537090/" rel="noopener noreferrer" target="_blank">Amazon</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/2/trackingcode/PRH5522E62429" rel="noopener noreferrer" target="_blank">Barnes &amp; Noble</a>, <a href="http://links.penguinrandomhouse.com/type/affiliate/isbn/9780525537090/siteID/8001/retailerid/6/trackingcode/penguinrandom" rel="noopener noreferrer" target="_blank">IndieBound</a>, and&nbsp;<a href="https://itunes.apple.com/us/book/how-to/id1451461524?mt=11" rel="noopener noreferrer" target="_blank">Apple Books</a>.</em></p>]]></content>
	<updated>2019-08-02T19:57:04+00:00</updated>
	<author><name>Randall</name></author>
	<source>
		<id>http://blog.xkcd.com</id>
		<link rel="self" href="http://blog.xkcd.com"/>
		<updated>2019-08-02T19:57:04+00:00</updated>
		<title>xkcd</title></source>

	<category term="uncategorized"/>


	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://0.gravatar.com/avatar/376789f08db9745413f487c5b8c4afcb?s=96&amp;d=identicon&amp;r=G"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-open.png?w=300"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-hole.png?w=300"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-move.png?w=300"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/blog-river.png?w=300"/>

	<link rel="enclosure" 
		type="image/generic" 
		length="1"
		href="https://blogdotxkcddotcom.files.wordpress.com/2019/08/ten_thousand.png"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-06-07:/380043</id>
	<link href="http://invisiblebread.com/2019/06/social/" rel="alternate" type="text/html"/>
	<title type="html">Social</title>
	<summary type="html"><![CDATA[<p>Get social! Or not! Dealer&rsquo;s choice!
&nbsp;
bonus panel</p>]]></summary>
	<content type="html"><![CDATA[<p><a href="https://invisiblebread.com/2019/06/social/" title="Social" rel="noopener noreferrer" target="_blank"><img src="https://invisiblebread.com/comics/2019-06-07-comic.png" alt="Social" title="Social" referrerpolicy="no-referrer"></a></p><p>Get social! Or not! Dealer&rsquo;s choice!</p>
<p>&nbsp;</p>
<br><br><a href="https://invisiblebread.com/bonus-panel/20190607/" rel="noopener noreferrer" target="_blank">bonus panel</a>]]></content>
	<updated>2019-06-07T10:00:00+00:00</updated>
	<author><name>Justin Boyd</name></author>
	<source>
		<id>http://invisiblebread.com</id>
		<link rel="self" href="http://invisiblebread.com"/>
		<updated>2019-06-07T10:00:00+00:00</updated>
		<title>Invisible Bread</title></source>

	<category term="comics"/>

	<category term="gatherings"/>

	<category term="social interaction"/>

	<category term="social situations"/>

	<category term="socializing"/>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-04-22:/376828</id>
	<link href="https://kottke.org/19/04/public-sans-a-new-typeface-from-the-us-government" rel="alternate" type="text/html"/>
	<title type="html">Public Sans, a New Typeface from the US Government</title>
	<summary type="html"><![CDATA[<p>As part of their recent announcement of a new web design system for US government website...</p>]]></summary>
	<content type="html"><![CDATA[<p><img src="https://tt-rss.goodevilgenius.org/plus/misc/images/public-sans.jpg" border="0" alt="Public Sans" referrerpolicy="no-referrer"></p>

<p>As part of <a href="https://v2.designsystem.digital.gov/whats-new/updates/2019/04/08/introducing-uswds-2-0/" rel="noopener noreferrer" target="_blank">their recent announcement</a> of a new web design system for US government websites, the General Services Administration has also introduced a new typeface called <a href="https://github.com/uswds/public-sans" rel="noopener noreferrer" target="_blank">Public Sans</a>.</p>

<blockquote><p>USWDS 2.0 adds built-in support for custom typefaces, and sometimes you need one that&rsquo;s simple, neutral, and isn&rsquo;t Helvetica. Public Sans is an open source, free license typeface (SIL Open Font License 1.1) designed and maintained by USWDS, adapted from Libre Franklin. Just as with our components, we intend Public Sans to be an example of how to design an accessible open source typeface with contributions and feedback from the public &mdash; to deliver a useful, neutral, sans serif and continuously improve it.</p></blockquote>

<p>Always interesting when typefaces are described as &ldquo;neutral&rdquo;. I&rsquo;ve never found that to be the case&hellip;</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/design" rel="noopener noreferrer" target="_blank">design</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/typography" rel="noopener noreferrer" target="_blank">typography</a>]]></content>
	<updated>2019-04-22T14:00:00+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2019-04-22T14:00:00+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-03-22:/374912</id>
	<link href="https://nullprogram.com/blog/2019/03/22/" rel="alternate" type="text/html"/>
	<title type="html">Endlessh: an SSH Tarpit</title>
	<summary type="html"><![CDATA[<p>This article was discussed on Hacker News, on reddit
(also), and featured in BSD Now 294.

...</p>]]></summary>
	<content type="html"><![CDATA[<p><em>This article was discussed <a href="https://news.ycombinator.com/item?id=19465967" rel="noopener noreferrer" target="_blank">on Hacker News</a>, <a href="https://old.reddit.com/r/programming/comments/b4iq00/endlessh_an_ssh_tarpit/" rel="noopener noreferrer" target="_blank">on reddit</a>
(<a href="https://old.reddit.com/r/netsec/comments/b4dwjl/endlessh_an_ssh_tarpit/" rel="noopener noreferrer" target="_blank">also</a>), and featured in <a href="https://www.youtube.com/watch?v=bM65iyRRW0A&amp;t=3m52s" rel="noopener noreferrer" target="_blank">BSD Now 294</a>.</em></p>

<p>I&rsquo;m a big fan of tarpits: a network service that intentionally inserts
delays in its protocol, slowing down clients by forcing them to wait.
This arrests the speed at which a bad actor can attack or probe the
host system, and it ties up some of the attacker&rsquo;s resources that
might otherwise be spent attacking another host. When done well, a
tarpit imposes more cost on the attacker than the defender.</p>

<p>The Internet is a very hostile place, and anyone who&rsquo;s ever stood up
an Internet-facing IPv4 host has witnessed the immediate and
continuous attacks against their server. I&rsquo;ve maintained <a href="https://tt-rss.goodevilgenius.org/blog/2017/06/15/" rel="noopener noreferrer" target="_blank">such a
server</a> for nearly six years now, and more than 99% of my
incoming traffic has ill intent. One part of my defenses has been
tarpits in various forms. The latest addition is an SSH tarpit I wrote
a couple of months ago:</p>

<p><a href="https://github.com/skeeto/endlessh" rel="noopener noreferrer" target="_blank"><strong>Endlessh: an SSH tarpit</strong></a></p>

<p>This program opens a socket and pretends to be an SSH server. However,
it actually just ties up SSH clients with false promises indefinitely
&mdash; or at least until the client eventually gives up. After cloning the
repository, here&rsquo;s how you can try it out for yourself (default port
2222):</p>

<div><div><pre><code>$ make
$ ./endlessh &amp;
$ ssh -p2222 localhost
</code></pre></div></div>

<p>Your SSH client will hang there and wait for at least several days
before finally giving up. Like a mammoth in the La Brea Tar Pits, it
got itself stuck and can&rsquo;t get itself out. As I write, my
Internet-facing SSH tarpit currently has 27 clients trapped in it. A
few of these have been connected for weeks. In one particular spike it
had 1,378 clients trapped at once, lasting about 20 hours.</p>

<p>My Internet-facing Endlessh server listens on port 22, which is the
standard SSH port. I long ago moved my real SSH server off to another
port where it sees a whole lot less SSH traffic &mdash; essentially none.
This makes the logs a whole lot more manageable. And (hopefully)
Endlessh convinces attackers not to look around for an SSH server on
another port.</p>

<p>How does it work? Endlessh exploits <a href="https://tools.ietf.org/html/rfc4253#section-4.2" rel="noopener noreferrer" target="_blank">a little paragraph in RFC
4253</a>, the SSH protocol specification. Immediately after the TCP
connection is established, and before negotiating the cryptography,
both ends send an identification string:</p>

<div><div><pre><code>SSH-protoversion-softwareversion SP comments CR LF
</code></pre></div></div>

<p>The RFC also notes:</p>

<blockquote>
  <p>The server MAY send other lines of data before sending the version
string.</p>
</blockquote>

<p>There is no limit on the number of lines, just that these lines must
not begin with &ldquo;SSH-&ldquo; since that would be ambiguous with the
identification string, and lines must not be longer than 255
characters including CRLF. So <strong>Endlessh sends and <em>endless</em> stream of
randomly-generated &ldquo;other lines of data&rdquo;</strong> without ever intending to
send a version string. By default it waits 10 seconds between each
line. This slows down the protocol, but prevents it from actually
timing out.</p>

<p>This means Endlessh need not know anything about cryptography or the
vast majority of the SSH protocol. It&rsquo;s dead simple.</p>

<h3>Implementation strategies</h3>

<p>Ideally the tarpit&rsquo;s resource footprint should be as small as
possible. It&rsquo;s just a security tool, and the server does have an
actual purpose that doesn&rsquo;t include being a tarpit. It should tie up
the attacker&rsquo;s resources, not the server&rsquo;s, and should generally be
unnoticeable. (Take note all those who write the awful &ldquo;security&rdquo;
products I have to tolerate at my day job.)</p>

<p>Even when many clients have been trapped, Endlessh spends more than
99.999% of its time waiting around, doing nothing. It wouldn&rsquo;t even be
accurate to call it I/O-bound. If anything, it&rsquo;s <em>timer-bound</em>,
waiting around before sending off the next line of data. <strong>The most
precious resource to conserve is <em>memory</em>.</strong></p>

<h4>Processes</h4>

<p>The most straightforward way to implement something like Endlessh is a
fork server: accept a connection, fork, and the child simply alternates
between <code>sleep(3)</code> and <code>write(2)</code>:</p>

<div><div><pre><code><span>for</span> <span>(;;)</span> <span>{</span>
    <span>ssize_t</span> <span>r</span><span>;</span>
    <span>char</span> <span>line</span><span>[</span><span>256</span><span>];</span>

    <span>sleep</span><span>(</span><span>DELAY</span><span>);</span>
    <span>generate_line</span><span>(</span><span>line</span><span>);</span>
    <span>r</span> <span>=</span> <span>write</span><span>(</span><span>fd</span><span>,</span> <span>line</span><span>,</span> <span>strlen</span><span>(</span><span>line</span><span>));</span>
    <span>if</span> <span>(</span><span>r</span> <span>==</span> <span>-</span><span>1</span> <span>&amp;&amp;</span> <span>errno</span> <span>!=</span> <span>EINTR</span><span>)</span> <span>{</span>
        <span>exit</span><span>(</span><span>0</span><span>);</span>
    <span>}</span>
<span>}</span>
</code></pre></div></div>

<p>A process per connection is a lot of overhead when connections are
expected to be up hours or even weeks at a time. An attacker who knows
about this could exhaust the server&rsquo;s resources with little effort by
opening up lots of connections.</p>

<h4>Threads</h4>

<p>A better option is, instead of processes, to create a thread per
connection. On Linux <a href="https://tt-rss.goodevilgenius.org/blog/2015/05/15/" rel="noopener noreferrer" target="_blank">this is practically the same thing</a>, but it&rsquo;s
still better. However, you still have to allocate a stack for the thread
and the kernel will have to spend some resources managing the thread.</p>

<h4>Poll</h4>

<p>For Endlessh I went for an even more lightweight version: a
single-threaded <code>poll(2)</code> server, analogous to stackless green threads.
The overhead per connection is about as low as it gets.</p>

<p>Clients that are being delayed are not registered in <code>poll(2)</code>. Their
only overhead is the socket object in the kernel, and another 78 bytes
to track them in Endlessh. Most of those bytes are used only for
accurate logging. Only those clients that are overdue for a new line
are registered for <code>poll(2)</code>.</p>

<p>When clients are waiting, but no clients are overdue, <code>poll(2)</code> is
essentially used in place of <code>sleep(3)</code>. Though since it still needs
to manage the <em>accept</em> server socket, it (almost) never actually waits
on <em>nothing</em>.</p>

<p>There&rsquo;s an option to limit the total number of client connections so
that it doesn&rsquo;t get out of hand. In this case it will stop polling the
accept socket until a client disconnects. I probably shouldn&rsquo;t have
bothered with this option and instead relied on <code>ulimit</code>, a feature
already provided by the operating system.</p>

<p>I could have used epoll (Linux) or kqueue (BSD), which would be much
more efficient than <code>poll(2)</code>. The problem with <code>poll(2)</code> is that it&rsquo;s
constantly registering and unregistering Endlessh on each of the
overdue sockets each time around the main loop. This is by far the
most CPU-intensive part of Endlessh, and it&rsquo;s all inflicted on the
kernel. Most of the time, even with thousands of clients trapped in
the tarpit, only a small number of them at polled at once, so I opted
for better portability instead.</p>

<p>One consequence of not polling connections that are waiting is that
disconnections aren&rsquo;t noticed in a timely fashion. This makes the logs
less accurate than I like, but otherwise it&rsquo;s pretty harmless.
Unforunately even if I wanted to fix this, the <code>poll(2)</code> interface
isn&rsquo;t quite equipped for it anyway.</p>

<h4>Raw sockets</h4>

<p>With a <code>poll(2)</code> server, the biggest overhead remaining is in the
kernel, where it allocates send and receive buffers for each client
and manages the proper TCP state. The next step to reducing this
overhead is Endlessh opening a <em>raw socket</em> and speaking TCP itself,
bypassing most of the operating system&rsquo;s TCP/IP stack.</p>

<p>Much of the TCP connection state doesn&rsquo;t matter to Endlessh and doesn&rsquo;t
need to be tracked. For example, it doesn&rsquo;t care about any data sent by
the client, so no receive buffer is needed, and any data that arrives
could be dropped on the floor.</p>

<p>Even more, raw sockets would allow for some even nastier tarpit tricks.
Despite the long delays between data lines, the kernel itself responds
very quickly on the TCP layer and below. ACKs are sent back quickly and
so on. An astute attacker could detect that the delay is artificial,
imposed above the TCP layer by an application.</p>

<p>If Endlessh worked at the TCP layer, it could <a href="https://nyman.re/super-simple-ssh-tarpit/" rel="noopener noreferrer" target="_blank">tarpit the TCP protocol
itself</a>. It could introduce artificial &ldquo;noise&rdquo; to the connection
that requires packet retransmissions, delay ACKs, etc. It would look a
lot more like network problems than a tarpit.</p>

<p>I haven&rsquo;t taken Endlessh this far, nor do I plan to do so. At the
moment attackers either have a hard timeout, so this wouldn&rsquo;t matter,
or they&rsquo;re pretty dumb and Endlessh already works well enough.</p>

<h3>asyncio and other tarpits</h3>

<p>Since writing Endless <a href="https://tt-rss.goodevilgenius.org/blog/2019/03/10/" rel="noopener noreferrer" target="_blank">I&rsquo;ve learned about Python&rsquo;s <code>asycio</code></a>, and
it&rsquo;s actually a near perfect fit for this problem. I should have just
used it in the first place. The hard part is already implemented within
<code>asyncio</code>, and the problem isn&rsquo;t CPU-bound, so being written in Python
<a href="https://tt-rss.goodevilgenius.org/blog/2019/02/24/" rel="noopener noreferrer" target="_blank">doesn&rsquo;t matter</a>.</p>

<p>Here&rsquo;s a simplified (no logging, no configuration, etc.) version of
Endlessh implemented in about 20 lines of Python 3.7:</p>

<div><div><pre><code><span>import</span> <span>asyncio</span>
<span>import</span> <span>random</span>

<span>async</span> <span>def</span> <span>handler</span><span>(</span><span>_reader</span><span>,</span> <span>writer</span><span>):</span>
    <span>try</span><span>:</span>
        <span>while</span> <span>True</span><span>:</span>
            <span>await</span> <span>asyncio</span><span>.</span><span>sleep</span><span>(</span><span>10</span><span>)</span>
            <span>writer</span><span>.</span><span>write</span><span>(</span><span>b</span><span>'</span><span>%</span><span>x</span><span>\r\n</span><span>'</span> <span>%</span> <span>random</span><span>.</span><span>randint</span><span>(</span><span>0</span><span>,</span> <span>2</span><span>**</span><span>32</span><span>))</span>
            <span>await</span> <span>writer</span><span>.</span><span>drain</span><span>()</span>
    <span>except</span> <span>ConnectionResetError</span><span>:</span>
        <span>pass</span>

<span>async</span> <span>def</span> <span>main</span><span>():</span>
    <span>server</span> <span>=</span> <span>await</span> <span>asyncio</span><span>.</span><span>start_server</span><span>(</span><span>handler</span><span>,</span> <span>'0.0.0.0'</span><span>,</span> <span>2222</span><span>)</span>
    <span>async</span> <span>with</span> <span>server</span><span>:</span>
        <span>await</span> <span>server</span><span>.</span><span>serve_forever</span><span>()</span>

<span>asyncio</span><span>.</span><span>run</span><span>(</span><span>main</span><span>())</span>
</code></pre></div></div>

<p>Since Python coroutines are stackless, the per-connection memory
overhead is comparable to the C version. So it seems asycio is
perfectly suited for writing tarpits! Here&rsquo;s an HTTP tarpit to trip up
attackers trying to exploit HTTP servers. It slowly sends a random,
endless HTTP header:</p>

<div><div><pre><code><span>import</span> <span>asyncio</span>
<span>import</span> <span>random</span>

<span>async</span> <span>def</span> <span>handler</span><span>(</span><span>_reader</span><span>,</span> <span>writer</span><span>):</span>
    <span>writer</span><span>.</span><span>write</span><span>(</span><span>b</span><span>'HTTP/1.1 200 OK</span><span>\r\n</span><span>'</span><span>)</span>
    <span>try</span><span>:</span>
        <span>while</span> <span>True</span><span>:</span>
            <span>await</span> <span>asyncio</span><span>.</span><span>sleep</span><span>(</span><span>5</span><span>)</span>
            <span>header</span> <span>=</span> <span>random</span><span>.</span><span>randint</span><span>(</span><span>0</span><span>,</span> <span>2</span><span>**</span><span>32</span><span>)</span>
            <span>value</span> <span>=</span> <span>random</span><span>.</span><span>randint</span><span>(</span><span>0</span><span>,</span> <span>2</span><span>**</span><span>32</span><span>)</span>
            <span>writer</span><span>.</span><span>write</span><span>(</span><span>b</span><span>'X-</span><span>%</span><span>x: </span><span>%</span><span>x</span><span>\r\n</span><span>'</span> <span>%</span> <span>(</span><span>header</span><span>,</span> <span>value</span><span>))</span>
            <span>await</span> <span>writer</span><span>.</span><span>drain</span><span>()</span>
    <span>except</span> <span>ConnectionResetError</span><span>:</span>
        <span>pass</span>

<span>async</span> <span>def</span> <span>main</span><span>():</span>
    <span>server</span> <span>=</span> <span>await</span> <span>asyncio</span><span>.</span><span>start_server</span><span>(</span><span>handler</span><span>,</span> <span>'0.0.0.0'</span><span>,</span> <span>8080</span><span>)</span>
    <span>async</span> <span>with</span> <span>server</span><span>:</span>
        <span>await</span> <span>server</span><span>.</span><span>serve_forever</span><span>()</span>

<span>asyncio</span><span>.</span><span>run</span><span>(</span><span>main</span><span>())</span>
</code></pre></div></div>

<p>Try it out for yourself. Firefox and Chrome will spin on that server
for hours before giving up. I have yet to see curl actually timeout on
its own in the default settings (<code>--max-time</code>/<code>-m</code> does work
correctly, though).</p>

<p>Parting exercise for the reader: Using the examples above as a starting
point, implement an SMTP tarpit using asyncio. Bonus points for using
TLS connections and testing it against real spammers.</p>]]></content>
	<updated>2019-03-22T17:26:45+00:00</updated>
	<author><name></name></author>
	<source>
		<id>http://nullprogram.com</id>
		<link rel="self" href="http://nullprogram.com"/>
		<updated>2019-03-22T17:26:45+00:00</updated>
		<title>null program</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-03-21:/374835</id>
	<link href="http://feedproxy.google.com/~r/Dudeiwantthat/~3/n3cOyp_fwMY/shark-kitchen-sponge-holder.asp" rel="alternate" type="text/html"/>
	<title type="html">Shark Kitchen Sponge Holder</title>
	<summary type="html"><![CDATA[]]></summary>
	<content type="html"><![CDATA[<img src="http://static.dudeiwantthat.com/img/household/kitchen/shark-kitchen-sponge-holder-37647.jpg" referrerpolicy="no-referrer"><img src="http://feeds.feedburner.com/~r/Dudeiwantthat/~4/n3cOyp_fwMY" alt="" referrerpolicy="no-referrer">]]></content>
	<updated>2019-03-21T17:19:00+00:00</updated>
	<author><name>info@dudeiwantthat.com Erin Carstens</name></author>
	<source>
		<id>http://www.dudeiwantthat.com</id>
		<link rel="self" href="http://www.dudeiwantthat.com"/>
		<updated>2019-03-21T17:19:00+00:00</updated>
		<title>DudeIWantThat.com</title></source>

	<category term="shark"/>

	<category term="kitchen"/>

	<category term="dishes"/>

	<category term="dishwasher"/>

	<category term="sponge"/>

	<category term="scrubber"/>


	<link rel="enclosure" 
		type="image/jpeg" 
		length="1000"
		href="http://static.dudeiwantthat.com/img/household/kitchen/shark-kitchen-sponge-holder-37647.jpg"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-03-21:/374786</id>
	<link href="https://www.blog.google/technology/ai/honoring-js-bach-our-first-ai-powered-doodle/" rel="alternate" type="text/html"/>
	<title type="html">Honoring J.S. Bach with our first AI-powered Doodle</title>
	<summary type="html"><![CDATA[<p>Ever wondered what Johann Sebastian Bach would sound like if he rocked out? You can find out by expl...</p>]]></summary>
	<content type="html"><![CDATA[<div><div><p>Ever wondered what Johann Sebastian Bach would sound like if he rocked out? You can find out by exploring <a href="http://www.google.com/doodles/celebrating-johann-sebastian-bach" rel="noopener noreferrer" target="_blank">today&rsquo;s AI-powered Google Doodle</a>, which honors Bach&rsquo;s birthday and legacy as one of the greatest composers of all time. A musician and composer during the Baroque period of the 18th century, Bach produced hundreds of compositions including cantatas, concertos, suites and chorales. In today&rsquo;s Doodle, you can create your own melody, and through the magic of machine learning, the Doodle will harmonize your melody in Bach&rsquo;s style. You can also explore inside the Doodle to see how the model Bach-ifys familiar tunes, or how your new collaboration might sound in a more modern rock style.</p></div></div><div><div><div><div><div><figure><a href="https://youtube.com/watch?v=XBfYPp6KF2g" rel="noopener noreferrer" target="_blank"><div><span>Made in partnership with the Google Magenta and Google PAIR teams, the Doodle is an interactive experience encouraging players to compose a two measure melody of their choice. With the press of a button, the Doodle then uses machine learning to harmonize the custom melody into Bach&rsquo;s signature music style.</span></div></a></figure></div></div></div></div><div><a href="https://youtube.com/watch?v=XBfYPp6KF2g" ng-cloak="" rel="noopener noreferrer" target="_blank"></a></div></div><div><div><p>Today&rsquo;s Doodle is the result of a collaboration between the Doodle, <a href="https://g.co/magenta" rel="noopener noreferrer" target="_blank">Magenta</a> and <a href="https://google.ai/pair" rel="noopener noreferrer" target="_blank">PAIR</a> teams at Google. The Magenta team aims to help people make music and art using machine learning, and PAIR produces tools or experiences to make machine learning enjoyable for everyone.</p><p>The first step in creating an AI-powered Doodle was building a machine learning model to power it. Machine learning is the process of teaching a computer to come up with its own answers by showing it a lot of examples, instead of giving it a set of rules to follow (as is done in traditional computer programming). <a href="https://ai.google/research/people/105787" rel="noopener noreferrer" target="_blank">Anna Huang</a>, an AI Resident on Magenta, developed Coconet, a model that can be used in a wide range of musical tasks&mdash;such as harmonizing melodies, creating smooth transitions between disconnected fragments of music and composing from scratch (check out more of these technical details in today&rsquo;s Magenta <a href="http://g.co/magenta/bach-doodle" rel="noopener noreferrer" target="_blank">blog post</a>). <br></p><p>Next, we personalized the model to match Bach&rsquo;s musical style. To do this, we trained Coconet on 306 of Bach&rsquo;s chorale harmonizations. His chorales always have four voices: each carries their own melodic line, creating a rich harmonic progression when played together. This concise structure makes the melodic lines good training data for a machine learning model. So when you create a melody of your own on the model in the Doodle, it harmonizes that melody in Bach's specific style.<br></p><p>Beyond the artistic and machine learning elements of the Doodle, we needed a lot of servers in order to make sure people around the world could use the Doodle. Historically, machine learning has been run on servers, which means that info is sent from a person&rsquo;s computer to data centers, and then the results are sent back to the computer. Using this same approach for the Bach Doodle would have generated a lot of back-and-forth traffic. <br></p><p>To make this work, we used PAIR&rsquo;s <a href="https://www.tensorflow.org/js/" rel="noopener noreferrer" target="_blank">TensorFlow.js</a>, which allows machine learning to happen entirely within an internet browser. However, for cases where someone&rsquo;s computer or device might not be fast enough to run the Doodle using TensorFlow.js, the machine learning model is run on Google&rsquo;s new <a href="https://cloud.google.com/tpu/" rel="noopener noreferrer" target="_blank">Tensor Processing Units</a> (TPUs), a way of quickly handling machine learning tasks in data centers. Today&rsquo;s Doodle is the first one ever to use TPUs in this way.<br></p><p>Head over to today&rsquo;s Doodle and find out what your collaboration with the famous composer sounds like!<br></p></div></div>]]></content>
	<updated>2019-03-21T04:00:00+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-03-21T04:00:00+00:00</updated>
		<title>Published articles</title></source>

	<category term="doodles"/>

	<category term="ai"/>


	<link rel="enclosure" 
		type="" 
		length="1"
		href="https://storage.googleapis.com/gweb-uniblog-publish-prod/images/CTA_highres.max-600x600.jpg"/>

</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-02-20:/372973</id>
	<link href="https://kottke.org/19/02/leaving-neverland" rel="alternate" type="text/html"/>
	<title type="html">Leaving Neverland</title>
	<summary type="html"><![CDATA[<p>Set to air on HBO starting March 3rd, Leaving Neverland is a two-part documentary film abou...</p>]]></summary>
	<content type="html"><![CDATA[<p>Set to air on HBO starting March 3rd, <a href="https://www.hbo.com/documentaries/leaving-neverland" rel="noopener noreferrer" target="_blank">Leaving Neverland</a> is a two-part documentary film about the experiences of two men who were befriended by and allegedly sexually abused by Michael Jackson as young boys. Here&rsquo;s the trailer:</p>

<p></p>

<blockquote><p>Leaving Neverland is a two-part documentary exploring the separate but parallel experiences of two young boys, James Safechuck, at age ten, and Wade Robson, at age seven, both of whom were befriended by Michael Jackson. Through gut-wrenching interviews with Safechuck, now 37, and Robson, now 41, as well as their mothers, wives and siblings, the film crafts a portrait of sustained abuse, exploring the complicated feelings that led both men to confront their experiences after both had a young son of his own.</p></blockquote>

<p>As <a href="https://www.nytimes.com/2019/01/31/arts/music/michael-jackson-timeline-sexual-abuse-accusations.html" rel="noopener noreferrer" target="_blank">this quick timeline of abuse allegations against Jackson</a> notes, both Safechuck and Robson previously denied that Jackson had abused them.</p>

<blockquote><p>Robson, by this point a choreographer for stars like Britney Spears, testified that he had spent the night at Neverland more than 20 times but that Jackson had never molested him or taken a shower with him.</p>

<p>James Safechuck, who had met Jackson as a young boy in the 1980s when he was cast in a Pepsi commercial, also denied publicly that he had been abused, although he was not called to testify.</p></blockquote>

<p>David Ehrlich saw the film at Sundance and <a href="https://www.indiewire.com/2019/01/leaving-neverland-review-michael-jackson-hbo-sundance-1202038317/" rel="noopener noreferrer" target="_blank">was completely convinced by the stories of the two men</a>.</p>

<blockquote><p>It may not be much of a secret that Michael Jackson acted inappropriately with a number of young boys, but there&rsquo;s no way to prepare yourself for the sickening forensic details presented in Dan Reed&rsquo;s four-hour expos&eacute;. It&rsquo;s one thing to be vaguely aware of the various allegations that were made against the King of Pop; the asterisks that will always be next to the late mega-star&rsquo;s name. It&rsquo;s quite another to hear the horrifyingly lucid testimony that stretches across the entire duration of &ldquo;Leaving Neverland,&rdquo; as two of Jackson&rsquo;s most repeat victims bravely lay bare how a universal icon seduced them away from their realities, splintered their families beyond all recognition, and leveraged their love for him into a disturbing litany of sexual acts.</p>

<p>The eloquent and straightforward &ldquo;Leaving Neverland&rdquo; was made for no other reason than to give shape to a nebulous cloud of rumors, many of which were floated in public before they were silenced behind settlements, and none of which a jury was able to prove beyond a reasonable doubt. In the wake of Reed&rsquo;s film and the shattering interview footage that it exists to share with us, there&rsquo;s no longer a reasonable doubt. There&rsquo;s no longer any doubt at all. Not only do the documentary&rsquo;s two main subjects perfectly corroborate their separate accounts in all of the most tragic of ways, but they do so with a degree of vulnerability that denies any room for skepticism.</p></blockquote>

<p>Other stars who previously had private or ignored abuse allegations leveled against them &mdash; Kevin Spacey, Bill Cosby, R. Kelly, Woody Allen, Louis CK &mdash; have been judged more harshly and their accusers have taken more seriously in recent years, and it&rsquo;ll be interesting to see what happens with Jackson after the documentary airs.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/HBO" rel="noopener noreferrer" target="_blank">HBO</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Leaving%20Neverland" rel="noopener noreferrer" target="_blank">Leaving Neverland</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/legal" rel="noopener noreferrer" target="_blank">legal</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Michael%20Jackson" rel="noopener noreferrer" target="_blank">Michael Jackson</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/movies" rel="noopener noreferrer" target="_blank">movies</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/trailers" rel="noopener noreferrer" target="_blank">trailers</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/TV" rel="noopener noreferrer" target="_blank">TV</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/video" rel="noopener noreferrer" target="_blank">video</a>]]></content>
	<updated>2019-02-20T15:12:49+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2019-02-20T15:12:49+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-02-05:/372051</id>
	<link href="https://kottke.org/19/02/how-to-absurd-scientific-advice-for-common-real-world-problems" rel="alternate" type="text/html"/>
	<title type="html">How To: Absurd Scientific Advice for Common Real-World Problems</title>
	<summary type="html"><![CDATA[<p>Randall Munroe, proprietor of the excellent XKCD and author of What If? and Thing Explain...</p>]]></summary>
	<content type="html"><![CDATA[<p><a href="https://www.amazon.com/exec/obidos/ASIN/0525537090/ref=nosim/0sil8" rel="noopener noreferrer" target="_blank"><img src="https://tt-rss.goodevilgenius.org/plus/misc/images/how-to-randall-munroe.jpg" border="0" alt="How To Randall Munroe" referrerpolicy="no-referrer"></a></p>

<p>Randall Munroe, proprietor of the excellent XKCD and author of <a href="https://www.amazon.com/exec/obidos/ASIN/0544272994/ref=nosim/0sil8" rel="noopener noreferrer" target="_blank">What If?</a> and <a href="https://www.amazon.com/exec/obidos/ASIN/0544668251/ref=nosim/0sil8" rel="noopener noreferrer" target="_blank">Thing Explainer</a>, is coming out with a new book in a few months called <a href="https://www.amazon.com/exec/obidos/ASIN/0525537090/ref=nosim/0sil8" rel="noopener noreferrer" target="_blank">How To: Absurd Scientific Advice for Common Real-World Problems</a>.</p>

<blockquote><p>Bestselling author and cartoonist Randall Munroe explains how to predict the weather by analyzing the pixels of your Facebook photos. He teaches you how to tell if you&rsquo;re a baby boomer or a 90&rsquo;s kid by measuring the radioactivity of your teeth. He offers tips for taking a selfie with a telescope, crossing a river by boiling it, and getting to your appointments on time by destroying the Moon. And if you want to get rid of the book once you&rsquo;re done with it, he walks you through your options for proper disposal, including dissolving it in the ocean, converting it to a vapor, using tectonic plates to subduct it into the Earth&rsquo;s mantle, or launching it into the Sun.</p></blockquote>

<p>Instant <a href="https://www.amazon.com/exec/obidos/ASIN/0525537090/ref=nosim/0sil8" rel="noopener noreferrer" target="_blank">pre-order</a>.</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/books" rel="noopener noreferrer" target="_blank">books</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/how%20to" rel="noopener noreferrer" target="_blank">how to</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/Randall%20Munroe" rel="noopener noreferrer" target="_blank">Randall Munroe</a>]]></content>
	<updated>2019-02-05T22:05:06+00:00</updated>
	<author><name>Jason Kottke</name></author>
	<source>
		<id>http://www.kottke.org/remainder/</id>
		<link rel="self" href="http://www.kottke.org/remainder/"/>
		<updated>2019-02-05T22:05:06+00:00</updated>
		<title>kottke.org</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-02-02:/371862</id>
	<link href="http://www.geekxgirls.com/article.php?ID=11161" rel="alternate" type="text/html"/>
	<title type="html">Parent Memes</title>
	<summary type="html"><![CDATA[<p>Parent Memes  &nbsp;LOL!  A relatable meme dump for all you parents out there...                    ...</p>]]></summary>
	<content type="html"><![CDATA[<div><h2><center>Parent Memes</center></h2>  &nbsp;<br><br>LOL!  A relatable meme dump for all you parents out there...  <br><br><center>   <img src="http://geekxgirls.com/images/_articles/parents-memes-01.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-02.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-03.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-04.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-05.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-06.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-07.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-08.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-09.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-10.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-11.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-12.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-13.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-14.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-15.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-16.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-17.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-18.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-19.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br><img src="http://geekxgirls.com/images/_articles/parents-memes-20.jpg" border="1" alt="Parent Memes" referrerpolicy="no-referrer"><br><br></center>   Artist: <a href="https://www.facebook.com/thatoneguywithglasses/posts/2325201127499211" target="_blank" rel="noopener noreferrer">That One Guy With Glasses</a>   <br><br><!-- AddThis Button BEGIN --><div> 	<a href="http://www.addthis.com/bookmark.php?v=250&amp;pubid=ra-4d6adb9a13c39374" rel="noopener noreferrer" target="_blank">Share</a> 	<span></span> 	<a href="http://www.geekxgirls.com/" rel="noopener noreferrer" target="_blank"></a> 	<a href="http://www.geekxgirls.com/" rel="noopener noreferrer" target="_blank"></a> 	<a href="http://www.geekxgirls.com/" rel="noopener noreferrer" target="_blank"></a> 	<a href="http://www.geekxgirls.com/" rel="noopener noreferrer" target="_blank"></a> 	</div> 	<!-- AddThis Button END --><center>Follow us on:<br><div> &nbsp;<a href="http://www.facebook.com/pages/Geek-Girls/188800427797025" target="_blank" title="Facebook" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/fbicon.png" border="0" referrerpolicy="no-referrer"></a> <a href="https://plus.google.com/110085100835706156336" border="0" target="_blank" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/google.png" border="0" referrerpolicy="no-referrer"></a> <a href="http://www.twitter.com/geekxgirls" target="_blank" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/twicon.png" border="0" referrerpolicy="no-referrer"></a> <a href="http://pinterest.com/geekxgirls" border="0" target="_blank" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/pintrest.png" border="0" referrerpolicy="no-referrer"></a> <a href="http://geekxgirls.tumblr.com/" border="0" target="_blank" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/tumblr.png" border="0" referrerpolicy="no-referrer"></a> <a href="http://www.youtube.com/user/geekxgirls" target="_blank" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/yticon.png" border="0" referrerpolicy="no-referrer"></a> <a href="http://instagram.com/geekxgirls" target="_blank" rel="noopener noreferrer"><img src="http://www.geekxgirls.com/images/instagram.jpg" border="0" referrerpolicy="no-referrer"></a> <!-- <a href='php/rss.php' target='_ '><img src='images/rss.png' border='0'></a> -->  </div></center><br><center>February 01 2019</center></div>]]></content>
	<updated>2019-02-02T04:00:13+00:00</updated>
	<author><name></name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-02-02T04:00:13+00:00</updated>
		<title>Published articles</title></source>


</entry>

<entry>
	<id>tag:tt-rss.goodevilgenius.org,2019-01-28:/371512</id>
	<link href="https://about.gitlab.com/2019/01/28/android-publishing-with-gitlab-and-fastlane/" rel="alternate" type="text/html"/>
	<title type="html">How to publish Android apps to the Google Play Store with GitLab and fastlane</title>
	<summary type="html"><![CDATA[<p>When we heard about fastlane, an app automation tool for delivering iOS and Android builds, we wante...</p>]]></summary>
	<content type="html"><![CDATA[<p>When we heard about <a href="https://fastlane.tools" rel="noopener noreferrer" target="_blank"><em>fastlane</em></a>, an app automation tool for delivering iOS and Android builds, we wanted to give it a spin to see if a combination of GitLab and <em>fastlane</em> could help us bring our mobile build and deployment automation to the next level. You can see an <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/pipelines/40768761" rel="noopener noreferrer" target="_blank">actual production deployment</a> of the <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app" rel="noopener noreferrer" target="_blank">Gitter Android app</a> that uses what we'll be implementing in this blog post; suffice to say, the results were fantastic and we've become big believers that the combination of GitLab and <em>fastlane</em> is a truly game-changing way to enable CI/CD for your mobile applications. With GitLab and <em>fastlane</em> we're getting, with minimal effort:</p>

<ul><li>Source control, project home, issue tracking, and everything else that comes with GitLab.</li>
  <li>Content and images (metadata) for Google Play Store listing managed in source control.</li>
  <li>Automatic signing, version numbers, and changelog.</li>
  <li>Automatic publishing to <code>internal</code> distribution channel in Google Play Store.</li>
  <li>Manual promotion through <code>alpha</code>, <code>beta</code>, and <code>production</code> channels.</li>
  <li>Containerized build environment, available in GitLab's container registry.</li>
</ul><p>If you'd like to jump ahead and see the finished product, you can take a look at the already-completed Gitter for Android <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/blob/master/.gitlab-ci.yml" rel="noopener noreferrer" target="_blank">.gitlab-ci.yml</a>, <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/blob/master/app/build.gradle" rel="noopener noreferrer" target="_blank">build.gradle</a>, <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/blob/master/Dockerfile" rel="noopener noreferrer" target="_blank">Dockerfile</a>, and <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/tree/master/fastlane" rel="noopener noreferrer" target="_blank"><em>fastlane</em> configuration</a>.</p>

<h2>Configuring <em>fastlane</em></h2>

<p>We'll begin first by setting up <em>fastlane</em> in our project, make a couple key changes to our Gradle configuration, and then wrap everything up in a GitLab pipeline.</p>

<p><em>fastlane</em> has pretty good <a href="https://docs.fastlane.tools/getting-started/android/setup/" rel="noopener noreferrer" target="_blank">documentation</a> to get you started, and if you run into platform-specific trouble it's the first place to check, but to get under way you really just need to complete a few straightforward steps.</p>

<h3>Initializing your project</h3>

<p>First up, you need to get <em>fastlane</em> installed locally and initialize your product. We're using the Ruby <code>fastlane</code> gem so you'll need Ruby on your system for this to work. You can read about <a href="https://docs.fastlane.tools/getting-started/android/setup/" rel="noopener noreferrer" target="_blank">other install options in the <em>fastlane</em> documentation</a>.</p>

<pre><code><span>source</span> <span>"https://rubygems.org"</span>

<span>gem</span> <span>"fastlane"</span>
</code></pre>
<p>Once your Gemfile is updated, you can run <code>bundle update</code> to update/generate your <code>Gemfile.lock</code>. From this point you can run <em>fastlane</em> by typing <code>bundle exec fastlane</code>. Later, you'll see that in CI we use <code>bundle install ...</code> to ensure the command runs within the context of our project environment.</p>

<p>Now that we have <em>fastlane</em> ready to run, we just need to initialize our repo with our configuration. Run <code>bundle exec fastlane init</code> from within your project directory, answer a few questions, and <em>fastlane</em> will create a new <code>./fastlane</code> directory containing its configuration.</p>

<h3>Setting up <em>supply</em></h3>

<p><em>supply</em> is a feature built into <em>fastlane</em> which will help you manage screenshots, descriptions, and other localized metadata/assets for publishing to the Google Play Store.</p>

<p>Please refer to these <a href="https://docs.fastlane.tools/getting-started/android/setup/#setting-up-supply" rel="noopener noreferrer" target="_blank">detailed instructions for collecting the credentials necessary to run <em>supply</em></a>.</p>

<p>Once you've set this up, simply run <code>bundle exec fastlane supply init</code> and all your current metadata will be downloaded from your store listing and saved in <code>fastlane/metadata/android</code>. From this point you're able to manage all of your store content as-code; when we publish a new version to the store later, the versions of content checked into your source repo will be used to populate the entry.</p>

<h3>Appfile</h3>

<p>The <code>./fastlane/Appfile</code> is pretty straightforward, and contains basic configuration you chose when you initialized your project. Later we'll see how to inject the <code>json_key_file</code> in your CI pipeline at runtime.</p>

<p><code>./fastlane/Appfile</code></p>
<pre><code><span>json_key_file("~/google_play_api_key.json")</span> <span># Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one</span>
<span>package_name("im.gitter.gitter")</span> <span># e.g. com.krausefx.app</span>
</code></pre>
<h3>Fastfile</h3>

<p>The <code>./fastlane/Fastfile</code> is more interesting, and contains the first changes you'll see that we made for Gitter vs. the default one created when you run <code>bundle exec fastlane init</code>.</p>

<p>The first section contains our definitions for how we want to run builds and tests. As you can see, this is pretty straightforward and builds right on top of your already set up Gradle tasks.</p>

<p><code>./fastlane/Fastfile</code></p>
<pre><code><span>default_platform(:android)</span>

<span>platform :android do</span>

  <span>desc "Builds the debug code"</span>
  <span>lane :buildDebug do</span>
    <span>gradle(task</span><span>:</span> <span>"</span><span>assembleDebug")</span>
  <span>end</span>

  <span>desc "Builds the release code"</span>
  <span>lane :buildRelease do</span>
    <span>gradle(task</span><span>:</span> <span>"</span><span>assembleRelease")</span>
  <span>end</span>

  <span>desc "Runs all the tests"</span>
  <span>lane :test do</span>
    <span>gradle(task</span><span>:</span> <span>"</span><span>test")</span>
  <span>end</span>

<span>...</span>
</code></pre>
<p>Creating Gradle tasks that publish/promote builds can be complicated and error prone, but <em>fastlane</em> makes this much easier by giving you pre-built commands (called Actions) that let you perform complex tasks with just a few simple actions.</p>

<p>In our example, we've set up a workflow where a new build can be published to the internal track and then optionally promoted through alpha, beta, and ultimately production. We initially had a new build for each track but it's safer to have the same/known build go through the whole process.</p>

<pre><code><span>...</span>

  <span>desc "Submit a new Internal Build to Play Store"</span>
  <span>lane :internal do</span>
    <span>upload_to_play_store(track</span><span>:</span> <span>'</span><span>internal'</span><span>,</span> <span>apk</span><span>:</span> <span>'</span><span>app/build/outputs/apk/release/app-release.apk')</span>
  <span>end</span>

  <span>desc "Promote Internal to Alpha"</span>
  <span>lane :promote_internal_to_alpha do</span>
    <span>upload_to_play_store(track</span><span>:</span> <span>'</span><span>internal'</span><span>,</span> <span>track_promote_to</span><span>:</span> <span>'</span><span>alpha')</span>
  <span>end</span>

  <span>desc "Promote Alpha to Beta"</span>
  <span>lane :promote_alpha_to_beta do</span>
    <span>upload_to_play_store(track</span><span>:</span> <span>'</span><span>alpha'</span><span>,</span> <span>track_promote_to</span><span>:</span> <span>'</span><span>beta')</span>
  <span>end</span>

  <span>desc "Promote Beta to Production"</span>
  <span>lane :promote_beta_to_production do</span>
    <span>upload_to_play_store(track</span><span>:</span> <span>'</span><span>beta'</span><span>,</span> <span>track_promote_to</span><span>:</span> <span>'</span><span>production')</span>
  <span>end</span>
<span>end</span>
</code></pre>
<p>An important note is that we've only scratched the surface of the kinds of actions that <em>fastlane</em> can automate. You can <a href="https://docs.fastlane.tools/actions/" rel="noopener noreferrer" target="_blank">read more about available actions here</a>, and it's even possible to create your own.</p>

<h2>Gradle configuration</h2>

<p>We also made a couple of key changes to our basic Gradle configuration to make publishing easier. Nothing major here, but it does help us make things run a little more smoothly.</p>

<h3>Secret properties</h3>

<p>The first changed section gathers the secret variables to be used for signing. These are either loaded via configuration file, or gathered from environment variables in the case of CI.</p>

<p><code>app/build.gradle</code></p>
<pre><code><span>// Try reading secrets from file</span>
<span>def</span> <span>secretsPropertiesFile</span> <span>=</span> <span>rootProject</span><span>.</span><span>file</span><span>(</span><span>"secrets.properties"</span><span>)</span>
<span>def</span> <span>secretProperties</span> <span>=</span> <span>new</span> <span>Properties</span><span>()</span>

<span>if</span> <span>(</span><span>secretsPropertiesFile</span><span>.</span><span>exists</span><span>())</span> <span>{</span>
    <span>secretProperties</span><span>.</span><span>load</span><span>(</span><span>new</span> <span>FileInputStream</span><span>(</span><span>secretsPropertiesFile</span><span>))</span>
<span>}</span>
<span>// Otherwise read from environment variables, this happens in CI</span>
<span>else</span> <span>{</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"oauth_client_id"</span><span>,</span> <span>"\"${System.getenv('oauth_client_id')}\""</span><span>)</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"oauth_client_secret"</span><span>,</span> <span>"\"${System.getenv('oauth_client_secret')}\""</span><span>)</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"oauth_redirect_uri"</span><span>,</span> <span>"\"${System.getenv('oauth_redirect_uri')}\""</span><span>)</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"google_project_id"</span><span>,</span> <span>"\"${System.getenv('google_project_id') ?: "</span><span>null</span><span>"}\""</span><span>)</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"signing_keystore_password"</span><span>,</span> <span>"${System.getenv('signing_keystore_password')}"</span><span>)</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"signing_key_password"</span><span>,</span> <span>"${System.getenv('signing_key_password')}"</span><span>)</span>
    <span>secretProperties</span><span>.</span><span>setProperty</span><span>(</span><span>"signing_key_alias"</span><span>,</span> <span>"${System.getenv('signing_key_alias')}"</span><span>)</span>
<span>}</span>
</code></pre>
<h3>Automatic versioning</h3>

<p>We also set up automatic versioning using environment variables <code>VERSION_CODE</code>, <code>VERSION_SHA</code>, which we will set up later in CI (locally they will just be <code>null</code> which is fine). Because each build's <code>versionCode</code> that you submit to the Google Play Store needs to be higher than the last, this makes it simple to deal with.</p>

<p><code>app/build.gradle</code></p>
<pre><code><span>android</span> <span>{</span>
    <span>defaultConfig</span> <span>{</span>
        <span>applicationId</span> <span>"im.gitter.gitter"</span>
        <span>minSdkVersion</span> <span>19</span>
        <span>targetSdkVersion</span> <span>26</span>
        <span>versionCode</span> <span>Integer</span><span>.</span><span>valueOf</span><span>(</span><span>System</span><span>.</span><span>env</span><span>.</span><span>VERSION_CODE</span> <span>?:</span> <span>0</span><span>)</span>
        <span>// Manually bump the semver version part of the string as necessary</span>
        <span>versionName</span> <span>"3.2.0-${System.env.VERSION_SHA}"</span>
</code></pre>
<h3>Signing configuration</h3>

<p>Finally, we inject the signing configuration which will automatically be used by Gradle to sign the release build. Depending on your configuration, you may already be doing this. We only worry about signing in the release build that would potentially be published to the Google Play Store.</p>

<blockquote>
  <p>When using App Signing by Google Play, you will use two keys: the app signing key and the upload key. You keep the upload key and use it to sign your app for upload to the Google Play Store.</p>

  <p><a href="https://developer.android.com/studio/publish/app-signing#google-play-app-signing" rel="noopener noreferrer" target="_blank"><em>https://developer.android.com/studio/publish/app-signing#google-play-app-signing</em></a></p>
</blockquote>

<blockquote>
  <p>IMPORTANT: Google will not re-sign any of your existing or new APKs that are signed with the app signing key. This enables you to start testing your app bundle in the internal test, alpha, or beta tracks while you continue to release your existing APK in production without Google Play changing it.</p>

  <p><em><code>https://play.google.com/apps/publish/?account=xxx#KeyManagementPlace:p=im.gitter.gitter&amp;appid=xxx</code></em></p>
</blockquote>

<p><code>app/build.gradle</code></p>
<pre><code>    <span>signingConfigs</span> <span>{</span>
        <span>release</span> <span>{</span>
            <span>// You need to specify either an absolute path or include the</span>
            <span>// keystore file in the same directory as the build.gradle file.</span>
            <span>storeFile</span> <span>file</span><span>(</span><span>"../android-signing-keystore.jks"</span><span>)</span>
            <span>storePassword</span> <span>"${secretProperties['signing_keystore_password']}"</span>
            <span>keyAlias</span> <span>"${secretProperties['signing_key_alias']}"</span>
            <span>keyPassword</span> <span>"${secretProperties['signing_key_password']}"</span>
        <span>}</span>
    <span>}</span>
    <span>buildTypes</span> <span>{</span>
        <span>release</span> <span>{</span>
            <span>minifyEnabled</span> <span>false</span>
            <span>testCoverageEnabled</span> <span>false</span>
            <span>proguardFiles</span> <span>getDefaultProguardFile</span><span>(</span><span>'proguard-android.txt'</span><span>),</span> <span>'proguard-rules.pro'</span>
            <span>signingConfig</span> <span>signingConfigs</span><span>.</span><span>release</span>
        <span>}</span>
    <span>}</span>
<span>}</span>
</code></pre>
<h2>Setting up the Docker build environment</h2>

<p>We are building a Docker image to be used as a repeatable, consistent build environment which will speed things up because it will already have the dependencies downloaded and installed. We're just fetching a few prerequisites, installing the Android SDK, and then grabbing <em>fastlane</em>.</p>

<p><code>Dockerfile</code></p>
<pre><code><span>FROM</span><span> openjdk:8-jdk</span>

<span># Just matched `app/build.gradle`</span>
<span>ENV</span><span> ANDROID_COMPILE_SDK "26"</span>
<span># Just matched `app/build.gradle`</span>
<span>ENV</span><span> ANDROID_BUILD_TOOLS "28.0.3"</span>
<span># Version from https://developer.android.com/studio/releases/sdk-tools</span>
<span>ENV</span><span> ANDROID_SDK_TOOLS "24.4.1"</span>

<span>ENV</span><span> ANDROID_HOME /android-sdk-linux</span>
<span>ENV</span><span> PATH="${PATH}:/android-sdk-linux/platform-tools/"</span>

<span># install OS packages</span>
<span>RUN </span>apt-get --quiet update --yes
<span>RUN </span>apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 build-essential ruby ruby-dev
<span># We use this for xxd hex-&gt;binary</span>
<span>RUN </span>apt-get --quiet install --yes vim-common
<span># install Android SDK</span>
<span>RUN </span>wget --quiet --output-document<span>=</span>android-sdk.tgz https://dl.google.com/android/android-sdk_r<span>${</span><span>ANDROID_SDK_TOOLS</span><span>}</span>-linux.tgz
<span>RUN </span>tar --extract --gzip --file<span>=</span>android-sdk.tgz
<span>RUN </span><span>echo </span>y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter android-<span>${</span><span>ANDROID_COMPILE_SDK</span><span>}</span>
<span>RUN </span><span>echo </span>y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter platform-tools
<span>RUN </span><span>echo </span>y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter build-tools-<span>${</span><span>ANDROID_BUILD_TOOLS</span><span>}</span>
<span>RUN </span><span>echo </span>y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-android-m2repository
<span>RUN </span><span>echo </span>y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-google_play_services
<span>RUN </span><span>echo </span>y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-m2repository
<span># install Fastlane</span>
COPY Gemfile.lock .
COPY Gemfile .
<span>RUN </span>gem install bundle
<span>RUN </span>bundle install
</code></pre>
<h2>Setting up GitLab</h2>

<p>With our build environment ready, let's set up our <code>.gitlab-ci.yml</code> to tie it all together in a CI/CD pipeline.</p>

<h3>Stages</h3>

<p>The first thing we do is define the stages that we're going to use. We'll set up our build environment, do our debug and release builds, run our tests, deploy to internal, and then promote through alpha, beta, and production. You can see that, apart from <code>environment</code>, these map to the lanes we set up in our <code>Fastfile</code>.</p>

<pre><code><span>stages</span><span>:</span>
  <span>-</span> <span>environment</span>
  <span>-</span> <span>build</span>
  <span>-</span> <span>test</span>
  <span>-</span> <span>internal</span>
  <span>-</span> <span>alpha</span>
  <span>-</span> <span>beta</span>
  <span>-</span> <span>production</span>
</code></pre>
<h3>Build environment update</h3>

<p>Next up we're going to update our build environment, if needed. If you're not familiar with <code>.gitlab-ci.yml</code> it may look like there's a lot going on here, but we'll take it one step at a time. The very first thing we do is set up an <code>.updateContainerJob</code> yaml template which can be used to capture shared configuration for other steps that want to use it. In this case, it will be used by the subsequent <code>updateContainer</code> and <code>ensureContainer</code> jobs.</p>

<h4><code>.updateContainerJob</code> template</h4>

<p>In this case, since we're dealing with Docker in Docker (<code>dind</code>), we are running some scripts which log into the local <a href="https://docs.gitlab.com/ee/user/project/container_registry.html" rel="noopener noreferrer" target="_blank">GitLab container registry</a>, fetch the latest image to be used as a layer cache reference, build a new image, and finally push the new version to the registry.</p>

<pre><code><span>.updateContainerJob</span><span>:</span>
  <span>image</span><span>:</span> <span>docker:stable</span>
  <span>stage</span><span>:</span> <span>environment</span>
  <span>services</span><span>:</span>
    <span>-</span> <span>docker:dind</span>
  <span>script</span><span>:</span>
    <span>-</span> <span>docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY</span>
    <span>-</span> <span>docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true</span>
    <span>-</span> <span>docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG .</span>
    <span>-</span> <span>docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG</span>
</code></pre>
<h4><code>updateContainer</code> job</h4>

<p>The first job that inherits <code>.updateContainerJob</code>, <code>updateContainer</code>, only runs if the <code>Dockerfile</code> was updated and will run through the template steps described above.</p>

<pre><code><span>updateContainer</span><span>:</span>
  <span>extends</span><span>:</span> <span>.updateContainerJob</span>
  <span>only</span><span>:</span>
    <span>changes</span><span>:</span>
      <span>-</span> <span>Dockerfile</span>
</code></pre>
<h4><code>ensureContainer</code> job</h4>

<p>Because the first pipeline on a branch can fail, the <code>only: changes: Dockerfile</code> syntax won't trigger for a subsequent pipeline after you fix things. This can leave your branch without a Docker image to use. So the <code>ensureContainer</code> job will look for an existing image and only build one if it doesn't exist. The one downside to this is that both of these jobs will run at the same time if it is a new branch.</p>

<p>Ideally, we could just use <code>$CI_REGISTRY_IMAGE:master</code> as a fallback when <code>$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG</code> isn't found but there isn't any syntax for this.</p>

<pre><code><span>ensureContainer</span><span>:</span>
  <span>extends</span><span>:</span> <span>.updateContainerJob</span>
  <span>allow_failure</span><span>:</span> <span>true</span>
  <span>before_script</span><span>:</span>
    <span>-</span> <span>"</span><span>mkdir</span><span> </span><span>-p</span><span> </span><span>~/.docker</span><span> </span><span>&amp;&amp;</span><span> </span><span>echo</span><span> </span><span>'{</span><span>\"</span><span>experimental</span><span>\"</span><span>:</span><span> </span><span>\"</span><span>enabled</span><span>\"</span><span>}'</span><span> </span><span>&gt;</span><span> </span><span>~/.docker/config.json"</span>
    <span>-</span> <span>docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY</span>
    <span># Skip update container `script` if the container already exists</span>
    <span># via https://gitlab.com/gitlab-org/gitlab-ce/issues/26866#note_97609397 -&gt; https://stackoverflow.com/a/52077071/796832</span>
    <span>-</span> <span>docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG &gt; /dev/null &amp;&amp; exit || true</span>
</code></pre>
<h3>Build and test</h3>

<p>With our build environment ready, we're ready to build our <code>debug</code> and <code>release</code> targets. Similar to above, we use a template to set up repeated steps within our build jobs, avoiding duplication. Within this section, the first thing we do is set the image to the build environment container image we built in the previous step.</p>

<h4><code>.build_job</code> template</h4>

<pre><code><span>.build_job</span><span>:</span>
  <span>image</span><span>:</span> <span>$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG</span>
  <span>stage</span><span>:</span> <span>build</span>

<span>...</span>
</code></pre>
<p>Next up is a step that's specific to Gitter, but if you use shared assets between a iOS and Android build you might consider doing something similar. What we're doing here is grabbing the latest mobile artifacts built by the web application pipeline and placing them in the appropriate location.</p>

<pre><code>  <span>before_script</span><span>:</span>
    <span>-</span> <span>wget --output-document=artifacts.zip --quiet "https://gitlab.com/gitlab-org/gitter/webapp/-/jobs/artifacts/master/download?job=mobile-asset-build"</span>
    <span>-</span> <span>unzip artifacts.zip</span>
    <span>-</span> <span>mkdir -p app/src/main/assets/www</span>
    <span>-</span> <span>mv output/android/www/* app/src/main/assets/www/</span>
</code></pre>
<p>Next, we use <a href="https://docs.gitlab.com/ee/ci/variables/README.html#variables" rel="noopener noreferrer" target="_blank">project-level variables</a> containing a binary (hex) dump of our signing keystore file and convert it back to a binary file. This allows us to inject the file into the build at runtime instead of checking it into source control, a potential security concern. To get the <code>signing_jks_file_hex</code> variable hex value, we use this binary -&gt; hex command, <code>xxd -p gitter-android-app.jks</code></p>

<pre><code>    <span># We store this binary file in a variable as hex with this command, `xxd -p gitter-android-app.jks`</span>
    <span># Then we convert the hex back to a binary file</span>
    <span>-</span> <span>echo "$signing_jks_file_hex" | xxd -r -p - &gt; android-signing-keystore.jks</span>
</code></pre>
<p>Here we're setting the version at runtime &ndash; these environment variables will be used by the Gradle build as implemented above. Because <code>$CI_PIPELINE_IID</code> increments on each pipeline, we can guarantee our <code>versionCode</code> is always higher than the last and be able to publish to the Google Play Store.</p>

<pre><code>    <span># We add 100 to get this high enough above current versionCodes that are published</span>
    <span>-</span> <span>"</span><span>export</span><span> </span><span>VERSION_CODE=$((100</span><span> </span><span>+</span><span> </span><span>$CI_PIPELINE_IID))</span><span> </span><span>&amp;&amp;</span><span> </span><span>echo</span><span> </span><span>$VERSION_CODE"</span>
    <span>-</span> <span>"</span><span>export</span><span> </span><span>VERSION_SHA=`echo</span><span> </span><span>${CI_COMMIT_SHORT_SHA}`</span><span> </span><span>&amp;&amp;</span><span> </span><span>echo</span><span> </span><span>$VERSION_SHA"</span>
</code></pre>
<p>Next, we automatically generate a changelog to include by copying whatever you have in <code>CURRENT_VERSION.txt</code> to the current <code>&lt;versionCode&gt;.text</code>. You can update <code>CURRENT_VERSION.txt</code> as necessary. I won't dive into the the details of the MR creation script here since it's somewhat specific to Gitter, but if you're interested in how something like this might work check out the <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/blob/master/ci-scripts/create-changlog-mr.sh" rel="noopener noreferrer" target="_blank"><code>create-changlog-mr.sh</code> script</a>.</p>

<pre><code>    <span># Make the changelog</span>
    <span>-</span> <span>cp ./fastlane/metadata/android/en-GB/changelogs/CURRENT_VERSION.txt "./fastlane/metadata/android/en-GB/changelogs/$VERSION_CODE.txt"</span>
    <span># We allow the remote push and MR creation to fail because the other job could create it</span>
    <span># and it's not strictly necessary (we just need the file locally for the CI build)</span>
    <span>-</span> <span>./ci-scripts/create-changlog-mr.sh || true</span>
    <span># Because we allow the MR creation to fail, just make sure we are back in the right repo state</span>
    <span>-</span> <span>git checkout "$CI_COMMIT_SHA"</span>
</code></pre>
<p>Just a couple of final items: First, whenever a build job is done, we remove the jks file just to be sure it doesn't get saved to artifacts, and second we set up the artifact directory from where the output of the build (<code>.apk</code>) will be saved.</p>

<pre><code>  <span>after_script</span><span>:</span>
    <span>-</span> <span>rm android-signing-keystore.jks || true</span>
  <span>artifacts</span><span>:</span>
    <span>paths</span><span>:</span>
    <span>-</span> <span>app/build/outputs</span>
</code></pre>
<h4><code>buildDebug</code> and <code>buildRelease</code> jobs</h4>

<p>Most of the complexity here was set up in the template, so as you can see our <code>buildDebug</code> and <code>buildRelease</code> job definitions are very clear. Both just call the appropriate <em>fastlane</em> task (which, if you remember, then calls the appropriate Gradle task). The <code>buildRelease</code> output is associated with the <code>production</code> environment so we can define an extra production-scoped set of <a href="https://docs.gitlab.com/ee/ci/variables/README.html#variables" rel="noopener noreferrer" target="_blank">project-level variables</a> which are different from our testing variables.</p>

<p>Since we set up code signing in the Gradle config (<code>build.gradle</code>) earlier, we can be confident here that our <code>release</code> builds are appropriately signed and ready for publishing.</p>

<pre><code>buildDebug:
  extends: .build_job
  script:
    - bundle exec fastlane buildDebug

buildRelease:
  extends: .build_job
  script:
    - bundle exec fastlane buildRelease
  environment:
    name: production
</code></pre>
<p>Testing is really just another instance of the same thing, but instead of calling one of the build lanes we call the test lane. Note that we are using a <code>dependency</code> from the <code>buildDebug</code> job to ensure we don't need to rebuild anything.</p>

<pre><code><span>testDebug</span><span>:</span>
  <span>image</span><span>:</span> <span>$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG</span>
  <span>stage</span><span>:</span> <span>test</span>
  <span>dependencies</span><span>:</span>
    <span>-</span> <span>buildDebug</span>
  <span>script</span><span>:</span>
    <span>-</span> <span>bundle exec fastlane test</span>
</code></pre>
<h3>Publish</h3>

<p>Now that our code is being built, we're ready to publish to the Google Play Store. We only <em>publish</em> to the <code>internal</code> testing track and <em>promote</em> this same build to the rest of the tracks.</p>

<p>This is achieved through the <em>fastlane</em> integration, using a pre-built action to handle the job. In this case we are using a <code>dependency</code> on the <code>buildRelease</code> job, and creating a local copy of the Google API JSON keyfile (again stored in a <a href="https://docs.gitlab.com/ee/ci/variables/README.html#variables" rel="noopener noreferrer" target="_blank">project-level variable</a> instead of checking it into source control.) We have this job (and all subsequent jobs) set to run only on <code>manual</code> action so we have full human control/intervention from this point forward. If you prefer to continuously deliver to your <code>internal</code> track you'd simply need to remove the <code>when: manual</code> entry and you'd have achieved your goal.</p>

<p>If you're like me, this may seem too easy to work. With everything we've configured in GitLab and <em>fastlane</em> to this point, it's really this simple!</p>

<pre><code><span>publishInternal</span><span>:</span>
  <span>image</span><span>:</span> <span>$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG</span>
  <span>stage</span><span>:</span> <span>internal</span>
  <span>dependencies</span><span>:</span>
    <span>-</span> <span>buildRelease</span>
  <span>when</span><span>:</span> <span>manual</span>
  <span>before_script</span><span>:</span>
    <span>-</span> <span>echo $google_play_service_account_api_key_json &gt; ~/google_play_api_key.json</span>
  <span>after_script</span><span>:</span>
    <span>-</span> <span>rm ~/google_play_api_key.json</span>
  <span>script</span><span>:</span>
    <span>-</span> <span>bundle exec fastlane internal</span>
</code></pre>
<h3>Promote</h3>

<p>As indicated earlier, promotion through alpha, beta, and production are all <code>manual</code> jobs. If internal testing is good, it can be promoted one step forward in sequence all the way through to production using these manual jobs.</p>

<p>If you're with me to this point, there's really nothing new here and this really highlights the power of GitLab with <em>fastlane</em>. We have a <code>.promote_job</code> template job which creates the local Google API JSON key file and the promote jobs themselves are basically identical.</p>

<pre><code><span>.promote_job</span><span>:</span>
  <span>image</span><span>:</span> <span>$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG</span>
  <span>when</span><span>:</span> <span>manual</span>
  <span>dependencies</span><span>:</span> <span>[]</span>
  <span>only</span><span>:</span>
    <span>-</span> <span>master</span>
  <span>before_script</span><span>:</span>
    <span>-</span> <span>echo $google_play_service_account_api_key_json &gt; ~/google_play_api_key.json</span>
  <span>after_script</span><span>:</span>
    <span>-</span> <span>rm ~/google_play_api_key.json</span>

<span>promoteAlpha</span><span>:</span>
  <span>extends</span><span>:</span> <span>.promote_job</span>
  <span>stage</span><span>:</span> <span>alpha</span>
  <span>script</span><span>:</span>
    <span>-</span> <span>bundle exec fastlane promote_internal_to_alpha</span>

<span>promoteBeta</span><span>:</span>
  <span>extends</span><span>:</span> <span>.promote_job</span>
  <span>stage</span><span>:</span> <span>beta</span>
  <span>script</span><span>:</span>
    <span>-</span> <span>bundle exec fastlane promote_alpha_to_beta</span>

<span>promoteProduction</span><span>:</span>
  <span>extends</span><span>:</span> <span>.promote_job</span>
  <span>stage</span><span>:</span> <span>production</span>
  <span>script</span><span>:</span>
    <span>-</span> <span>bundle exec fastlane promote_beta_to_production</span>
</code></pre>
<p>Note that we're <code>only</code> allowing production promotion from the <code>master</code> branch, instead of from any branch. This is to ensure that the production build uses the separate set of <code>production</code> environment variables which only happens for the <code>buildRelease</code> job. We also have these <a href="https://docs.gitlab.com/ee/ci/variables/#protected-variables" rel="noopener noreferrer" target="_blank">variables set as protected</a> so we can enforce that they are only used on the <code>master</code> branch which is protected.</p>

<h3>Variables</h3>

<p>The last step is to make sure you set up the <a href="https://docs.gitlab.com/ee/ci/variables/README.html#variables" rel="noopener noreferrer" target="_blank">project-level variables</a> we used throughout the configuration above:</p>

<ul><li><code>google_play_service_account_api_key_json</code>: see <a href="https://docs.fastlane.tools/getting-started/android/setup/#collect-your-google-credentials" rel="noopener noreferrer" target="_blank">https://docs.fastlane.tools/getting-started/android/setup/#collect-your-google-credentials</a></li>
  <li><code>oauth_client_id</code></li>
  <li><code>oauth_client_id</code>, protected, <code>production</code> environment</li>
  <li><code>oauth_client_secret</code></li>
  <li><code>oauth_client_secret</code>, protected, <code>production</code> environment</li>
  <li><code>oauth_redirect_uri</code></li>
  <li><code>oauth_redirect_uri</code>, protected, <code>production</code> environment</li>
  <li><code>signing_jks_file_hex</code>: <code>xxd -p gitter-android-app.jks</code></li>
  <li><code>signing_key_alias</code></li>
  <li><code>signing_key_password</code></li>
  <li><code>signing_keystore_password</code></li>
</ul><p>If you are using the same <a href="https://gitlab.com/gitlab-org/gitter/gitter-android-app/blob/master/ci-scripts/create-changlog-mr.sh" rel="noopener noreferrer" target="_blank"><code>create-changlog-mr.sh</code> script</a> as us,</p>

<ul><li><code>deploy_key_android_repo</code>: see <a href="https://docs.gitlab.com/ee/user/project/deploy_tokens/" rel="noopener noreferrer" target="_blank">https://docs.gitlab.com/ee/user/project/deploy_tokens/</a></li>
  <li><code>gitlab_api_access_token</code>: see <a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html" rel="noopener noreferrer" target="_blank">https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html</a> (we use a bot user)</li>
</ul><p><img src="https://tt-rss.goodevilgenius.org/images/blogimages/android-fastlane-variables.png" alt="Project variables for Gitter for Android" referrerpolicy="no-referrer"></p>

<h2>What's next</h2>

<p>Using this configuration we've got Gitter for Android building, signing, deploying to our internal track, and publishing to production as frequently as we like. Next up will be to do the same for iOS, so watch this space for our next post!</p>

<p>Photo by <a href="https://unsplash.com/@impatrickt" rel="noopener noreferrer" target="_blank">Patrick Tomasso</a> on <a href="https://unsplash.com/photos/KGcLJwIYiac" rel="noopener noreferrer" target="_blank">Unsplash</a></p>
<img src="https://about.gitlab.com/images/blogimages/android-fastlane-pipeline.png" referrerpolicy="no-referrer">]]></content>
	<updated>2019-01-28T00:00:00+00:00</updated>
	<author><name>Jason Lenny</name></author>
	<source>
		<id>https://tt-rss.goodevilgenius.org</id>
		<link rel="self" href="https://tt-rss.goodevilgenius.org"/>
		<updated>2019-01-28T00:00:00+00:00</updated>
		<title>Published articles</title></source>


</entry>


</feed>
<!-- vim:ft=xml
	  -->
