<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>vince falconi | tattooed.dev</title>
	<subtitle>My name is Vince. I make things on the web.</subtitle>
	<link href="https://www.tattooed.dev/feed.xml" rel="self"/>
	<link href="https://www.tattooed.dev/" />
	
	<updated>2023-10-10T01:45:00Z</updated>
	<id>urn:uuid:8dd210db-242a-42ac-b9f2-ca7d6468b4c8</id>
	<author>
		<name>vince falconi</name>
	</author>
	
	
		
			
			<entry>
				<title>The broken election URL</title>
				<link href="https://www.tattooed.dev/wrote/the-broken-election-url/"/>
				<updated>2023-10-10T01:45:00Z</updated>
				<id>urn:uuid:14aadc37-f9d9-4c0b-8c6e-1f2312492e75</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;Today was the last day to register for voting in this November&amp;#39;s election. Ever the responsible citizen and devoted &amp;quot;I Voted&amp;quot; sticker-obtainer, I triple-checked my voter registration.&lt;/p&gt;
&lt;p&gt;Using the &lt;a href=&quot;https://sos.ms.gov&quot;&gt;Missisippi Secretary of State&amp;#39;s website&lt;/a&gt;, you can find almost anything you could want for voter registration. This includes &lt;a href=&quot;https://sos.ms.gov/sites/default/files/yall_vote_icons/yall_vote_pdfs/voter_registration_application.pdf&quot;&gt;a PDF&lt;/a&gt; of a mail-in voter registration application, which includes some helpful information including the thing you need to have to vote: voter ID.&lt;/p&gt;
&lt;p&gt;I could yell for hours about how terrible voter ID is&amp;mdash;about how it is designed to disenfranchise people, particularly poor and not-white people, and how additional friction between a person and their vote is a by definition voter suppression&amp;mdash;but that&amp;#39;s not what we&amp;#39;re here for today.&lt;/p&gt;
&lt;p&gt;Voter ID is called out as a requirement, right there in the PDF, and the Secretary&amp;#39;s office has included a link to a web page to aid in the process of acquiring a valid ID. That&amp;#39;s great!&lt;/p&gt;
&lt;p&gt;That link is [&lt;a href=&quot;http://www.MSVoterID.sos.gov%5D&quot;&gt;www.MSVoterID.sos.gov]&lt;/a&gt; and, as of October 9, 2023, if you open that link, you will likely see a warning about the security of your connection to the website with information concerning the exercise of your right to vote.&lt;/p&gt;

							
						
							
								
	

	
		
		
			&lt;picture&gt;
				
				&lt;img
					class=&quot;blog-image--block-center&quot;
					src=&quot;https://assets.tattooed.dev/images/sos-voter-id-error.jpg&quot;
					width=&quot;1158&quot;
					height=&quot;1331&quot;
					loading=&quot;lazy&quot;
					alt=&quot;A private browser window showing a security warning about the validity of a website&amp;#39;s certificate. Screenshot.&quot; /&gt;
			&lt;/picture&gt;
		
	
	

							
						
							
								&lt;p&gt;Well, that&amp;#39;s not great.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re not familiar with this error or TLS certificates in general, here&amp;#39;s the basics: a TLS certificate is document browsers use to make sure information exchanged between your computer and the server is seen only by you and the server, and has not been tampered with in transit over the internet. A certificate is only valid when used on the domains it is issued for, and has both an issue and an expiration date. A certificate is issued by what is called a Certificate Authority or CA, and if a browser does not know or trust the certificate&amp;#39;s issuing CA, the browser considers the certificate invalid. &lt;/p&gt;
&lt;p&gt;The voter ID link results in an &amp;quot;invalid certificate&amp;quot; error message because the certificate was issued for any domain that matches the pattern &lt;code&gt;*.sos.ms.gov&lt;/code&gt;, and &lt;code&gt;www.MSVoterID.ms.gov&lt;/code&gt; fails to match the pattern.&lt;/p&gt;
&lt;p&gt;If a certificate is invalid, a visitor can usually still access the site as long as they know not to trust the site with sensitive information. However, the Secretary of State&amp;#39;s website uses something called HTTP Strict Transport Security or HSTS, which tells a browser to change all URLs from HTTP to HTTPS before sending the request to the server, and also to break the connection completely if a redirect goes from HTTPS to HTTP or if it encounters an invalid certificate along the way.&lt;/p&gt;
&lt;p&gt;Ultimately, &lt;code&gt;www.MSVoterID.ms.gov&lt;/code&gt; redirects to a page on the Secretary of State&amp;#39;s site dedicated to voter ID, exactly as promised, but because its redirect chain includes an invalid certificate, anyone using Chrome, Edge, Brave, Opera, Arc, or Safari browsers are not going to get there. (Oddly enough, Firefox ignores the invalid certificate and redirects unbothered. So, that&amp;#39;s &lt;strong&gt;fun&lt;/strong&gt;.)&lt;/p&gt;
&lt;p&gt;Why is this a problem? I mean, the voter ID information is freely available on the Secretary&amp;#39;s website, plain as day. If a voter wants to get that information, they can just open a new tab and pull it up and be done with it.&lt;/p&gt;
&lt;p&gt;If this information is available, why is this a problem?&lt;/p&gt;
&lt;p&gt;We&amp;#39;re talking about the official application to exercise one&amp;#39;s right to vote in the state of Mississippi, and it contains false information. This state form explains two ways to contact the office, and the easier of the two is broken in the best scenario and untrustworthy in the worst.&lt;/p&gt;
&lt;p&gt;Moreover, encountering a warning about the security of an elections website is not a great look. That certificate error has nothing to do with the voting machines used by the state, but I argue that it does not inspire confidence in the average person focused in on &amp;quot;security&amp;quot; and the alarm imagery.&lt;/p&gt;
&lt;p&gt;It is entirely reasonable to write it off as one more broken link on the internet, but again, anything that makes it more difficult to vote is, by definition, voter suppression. According to the law, the right to vote is a given. We don&amp;#39;t have to complete a task to have the right, but for some reason, we have to complete multiple tasks to exercise the right.&lt;/p&gt;
&lt;p&gt;Working around that registration form&amp;#39;s broken link is just one more requirement.&lt;/p&gt;
&lt;p&gt;I contacted the Secretary of State&amp;#39;s office to let them know. We&amp;#39;ll see if they fix it.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>I farted on a website</title>
				<link href="https://www.tattooed.dev/wrote/i-farted-on-a-website/"/>
				<updated>2023-05-26T05:00:00Z</updated>
				<id>urn:uuid:0b0e14ae-424f-4395-a166-c470f89a1323</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;The year was 2021. The month was February. The day was Sunday. I don&amp;#39;t remember why, but I sat down at my desk and despite my tightly-held personal &amp;quot;no work email on the weekend&amp;quot; policy, I opened my work email.&lt;/p&gt;
&lt;p&gt;I found a short email thread from my web designer counterpart, our manager, and our director. A person applying for a job with our company left us a note in the contact form, informing us that our website appeared to be tampered with because an odd string was appearing on every form on the site.&lt;/p&gt;
&lt;p&gt;The discussion had already concluded by the time I read it, deciding it was likely a phishing attempt or a phishing test, and besides, no one could see the string the person claimed to find.&lt;/p&gt;
&lt;p&gt;I shave with Occam&amp;#39;s Razor as often as the next person but, while rare, sometimes the correct explanation &lt;em&gt;isn&amp;#39;t&lt;/em&gt; the simplest. Outliers exist. The outlier in this situation?&lt;/p&gt;
&lt;p&gt;The phrase &amp;quot;fart noises.&amp;quot;&lt;/p&gt;
&lt;p&gt;Yeah.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;#39;d just finished up a big project updating the forms on the company&amp;#39;s website.&lt;/p&gt;
&lt;p&gt;These forms were my baby, a thing I&amp;#39;d built with love and intention over ten years, to work the way we needed them, crossing all the compliance boxes and doing the spam prevention while still providing a frictionless sign-up flow no matter the context.&lt;/p&gt;
&lt;p&gt;This project became particularly frustrating when I had to account for geo-based fields and messaging. If I couldn&amp;#39;t tell whether something was working, I needed a signal in the front-end to let me know.&lt;/p&gt;
&lt;p&gt;Most developers have their go-to test strings. &amp;quot;Hello world.&amp;quot; &amp;quot;Test123.&amp;quot; &amp;quot;This worked.&amp;quot;&lt;/p&gt;
&lt;p&gt;Me? My test strings worked on an escalating scale that reflected my annoyance and desperation. Let us think of it as a form of developer catharsis.&lt;/p&gt;
&lt;p&gt;At the start of a project, my test strings look a lot like the previous standard fare, but when something is giving me a headache, I switch to &amp;quot;fart noises.&amp;quot; Because that&amp;#39;s what is in my heart. The problem feels like a fart noise. Hell, as the headache reaches a migraine, a curse word or two may even make an appearance, but no matter the level of annoyance they reflected, they are cleaned up in the end in a find-and-replace two-step.&lt;/p&gt;
&lt;p&gt;Except I forgot to do that two-step this go around.&lt;/p&gt;
&lt;p&gt;Two years out, I can try and fail to scrape every last square inch of my brain to find why or how I forgot, but to mangle a cliché, it is what it was.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Yeah.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The second I read &amp;quot;fart noises&amp;quot; in the email that Sunday afternoon, I opened the site repo and searched. Found it in the global form partial used on every page, tucked inside a conditional block that would only show it to visitors outside of the United States.&lt;/p&gt;
&lt;p&gt;Naturally, no one in that email thread thought to check from a proxy connection. &amp;quot;If it wasn&amp;#39;t appearing,&amp;quot; they may have thought, &amp;quot;it had to be made-up.&amp;quot;&lt;/p&gt;
&lt;p&gt;Within minutes of reading the email, I somehow managed to reply, &amp;quot;Sorry to dampen the mood, but they&amp;#39;re right. That is one of my test strings. It made it out of testing and QA. I&amp;#39;m pushing out a fix right now to remove it. I am mortified.&amp;quot;&lt;/p&gt;
&lt;p&gt;My early-morning meeting with our Chief Marketing Officer was cut short by a (100% unrelated) config error by our MSP that took our website offline. But based on conversations that followed, I was going to get it. What &amp;quot;it&amp;quot; was, I&amp;#39;ll never know, but I was going to get it.&lt;/p&gt;
&lt;p&gt;I&amp;#39;d never made a mistake like that. It was embarrassing&amp;mdash;for me, and for the company. Calling out specific examples feels cruel, but we&amp;#39;ve all heard stories of dropped tables in production, we&amp;#39;ve watched DNS remind everyone that it&amp;#39;s not here to make friends. Somehow, more than a decade into my career, I&amp;#39;d managed to avoid writing a story of my very own.&lt;/p&gt;
&lt;p&gt;Then I farted on the marketing website of a well-known, well-respected security software company.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is the part of the Blog Post&amp;trade; where you&amp;#39;re probably expecting some key takeaways, what lessons were learned, other thought-leadership type stuff. Maybe the announcement of an open-source QA solution inspired by this incident. &lt;em&gt;Something.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But that’s not what we’re here to do today.&lt;/p&gt;
&lt;p&gt;Maybe more eyes in the stakeholder review cycles would have prevented this.&lt;/p&gt;
&lt;p&gt;Maybe a tighter QA process would have prevented this.&lt;/p&gt;
&lt;p&gt;Maybe a better work-life balance would have reduced the pressure and helped me to&amp;mdash;&lt;/p&gt;
&lt;p&gt;&amp;mdash;but none of those things were in place. It happened.&lt;/p&gt;
&lt;p&gt;I could have tried harder.&lt;/p&gt;
&lt;p&gt;I could have cared more.&lt;/p&gt;
&lt;p&gt;I could have&amp;mdash;&lt;/p&gt;
&lt;p&gt;&amp;mdash;AND YET. I worked hard. I cared deeply. And it happened.&lt;/p&gt;
&lt;p&gt;I farted on a website.&lt;/p&gt;
&lt;p&gt;The perfect QA process doesn&amp;#39;t exist.&lt;/p&gt;
&lt;p&gt;Mistakes will happen.&lt;/p&gt;
&lt;p&gt;It wasn&amp;#39;t the result of a personal shortfall.&lt;/p&gt;
&lt;p&gt;Mistakes will continue to happen.&lt;/p&gt;
&lt;p&gt;And when they do, remember, that mistake is already in the past. So, how do you respond in the present?&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Blocking pachyderms</title>
				<link href="https://www.tattooed.dev/wrote/blocking-pachyderms/"/>
				<updated>2023-03-15T17:43:00Z</updated>
				<id>urn:uuid:ad43ab43-6153-49fb-b07b-28ed649cb4a1</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;Openly and with wild abandon, I block ads and tracking with a self-hosted &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard&lt;/a&gt; instance on my home network. Sometimes I lose functionality, like when a Vimeo-based streaming app on AppleTV refuses to load any content because a tracking script fails, but for the most part, it&amp;#39;s a successful scorched earth approach to living on the internet.&lt;/p&gt;
&lt;p&gt;One of my favorite uses of an ab blocker is blocking distractions I can&amp;#39;t stop myself. If you are using my wifi, you won&amp;#39;t be able to scroll TikTok for hours on end&amp;mdash;I did that a few times too many and now no one can. Here. At my house.&lt;/p&gt;
&lt;p&gt;All of Meta&amp;#39;s domains, blocked. Twitter? Blocked.&lt;/p&gt;
&lt;p&gt;With my old ad blocker (&lt;a href=&quot;https://pi-hole.net/&quot;&gt;PiHole&lt;/a&gt;), you could create a custom list of domains to block, which sounds great until you think about how many domains a single service uses. It&amp;#39;s a lot to keep current. I like AdGuard because of its &amp;quot;Blocked Services&amp;quot; interface which lets you toggle a switch and keep, say, Amazon out of your network.&lt;/p&gt;
&lt;p&gt;The one service in the list I didn&amp;#39;t quite understand, though, was Mastodon. If Mastodon is federated, how can you block all of it?&lt;/p&gt;
&lt;p&gt;So I looked at &lt;a href=&quot;https://github.com/AdguardTeam/AdguardHome&quot;&gt;the source&lt;/a&gt;, which has no reference to Mastodon, but searching the entire organization, I found &lt;a href=&quot;https://github.com/AdguardTeam/HostlistsRegistry&quot;&gt;the tool that generates AdGuard&amp;#39;s blocklists&lt;/a&gt;, and &lt;a href=&quot;https://github.com/AdguardTeam/HostlistsRegistry/blob/main/hostlists-builder/mastodon.js&quot;&gt;Mastodon&amp;#39;s server list specifically&lt;/a&gt;. My original assumption was that AdGuard would block the obvious core Mastodon-branded servers (think &lt;code&gt;mastodon.social&lt;/code&gt;, etc), but I could not have been more wrong: AdGuard queries the JoinMastodon.org server list, sorts by size, and returns the top 100 largest instances, which are then blocked.&lt;/p&gt;
&lt;p&gt;I don&amp;#39;t believe this server list is part of the Mastodon API, but it existing seems pretty helpful. And a general round of round of applause for open-source software.&lt;/p&gt;
&lt;p&gt;Now I&amp;#39;m off to block Mastodon so I can get some work done today.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Cabinet of Cursed CSS: The Double-Negative Selector</title>
				<link href="https://www.tattooed.dev/wrote/cabinet-of-cursed-css-the-double-negative-selector/"/>
				<updated>2023-02-22T05:55:00Z</updated>
				<id>urn:uuid:b92bba05-d73a-4f68-a781-324f508be8f9</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;Years ago, when I made my CMS take the jump from WYSIWYG to Markdown, I found myself in a bit of a situation.&lt;/p&gt;
&lt;p&gt;I wanted to style lists in blog posts a certain way, but I also liked using single classes for my CSS. Markdown did not let me do that without marking up the list myself every time. Not only am I lazy, I do not memorize class names. I could make all lists on this site have the same styles and let classes override them, but that&amp;#39;s extra code and if there&amp;#39;s one thing I like more than single-class selectors, it&amp;#39;s a clean view of style inheritance in dev tools. I could just scope an &lt;code&gt;ul&lt;/code&gt;/&lt;code&gt;ol&lt;/code&gt; selector with &lt;code&gt;.blog-post&lt;/code&gt; and call it a day, but now we&amp;#39;re talking about upending my ideal form of CSS.&lt;/p&gt;
&lt;p&gt;In my own house? No, I don&amp;#39;t think so.&lt;/p&gt;
&lt;p&gt;I took this personally. And when I take things personally, I overthink. And when I overthink, I make complicated solutions of questionable value. And when I make complicated solutions of a questionable value, I share them here.&lt;/p&gt;
&lt;p&gt;I had my constraints:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Style lists in a blog post&lt;/li&gt;
&lt;li&gt;Do not require a scoping class&lt;/li&gt;
&lt;li&gt;Do not create styles that need to be overridden by a class in other contexts&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first question was, how do I select specific elements without scoping or applying classes directly? What do those elements look like&amp;mdash;how can I find a list element that doesn&amp;#39;t have a style? To my knowledge, CSS does not provide a way to select an element that has not been selected previously, so we need a clue. For me, that clue is the absence of a class or an inline style:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ul:not([class]):not([style]),
ol:not([class]):not([style])&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;I call it the double-negative selector.&lt;/p&gt;
&lt;p&gt;This checks all three requirements: lists in blog posts lack both &lt;code&gt;class&lt;/code&gt; and &lt;code&gt;style&lt;/code&gt; (with all due respect of course), so this will apply to them, it doesn&amp;#39;t need a scoping class to do that, and there is nothing to override because any styles applied with this selector disappear entirely once a class or an inline style are applied.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s 2023. We have new tools. We can do better.&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:where(ol, ul):not(:where([class],[style]))&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Using &lt;code&gt;:where()&lt;/code&gt; removes the duplication for each type of list, love to keep things simple around here.&lt;/p&gt;
&lt;p&gt;If we didn&amp;#39;t change the attribute selectors, too, I&amp;#39;d be short-changing only myself.&lt;/p&gt;
&lt;p&gt;Extending the use of &lt;code&gt;:where()&lt;/code&gt; into the attribute selectors (&lt;code&gt;:not(:where([class], [style]))&lt;/code&gt;) achieves the same result as using a list of selectors (&lt;code&gt;:not([class], [style])&lt;/code&gt;), but &amp;quot;selectors inside &lt;code&gt;:where()&lt;/code&gt; have specificity 0&amp;quot; (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:where#:~:text=selectors%20inside%20%3Awhere()%20have%20specificity%200&quot;&gt;MDN&lt;/a&gt;), so the entire selector ends up with a specificity of &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The specificity isn&amp;#39;t much use here because the styles will disappear in the presence of a class or inline style, but if I&amp;#39;m going to make things weird, I&amp;#39;m not leaving anything on the table.&lt;/p&gt;
&lt;p&gt;Speaking of...&lt;/p&gt;
&lt;h2 id=&quot;the-cursed-css&quot;&gt;The cursed CSS&lt;/h2&gt;
&lt;p&gt;A few months ago, I &lt;a href=&quot;/wrote/i-has-an-idea-about-lists/&quot;&gt;wrote about using &lt;code&gt;:has()&lt;/code&gt;&lt;/a&gt; to define columns for a list based on the length of its contents&amp;mdash;one column for every twenty items until 60, then it&amp;#39;s stuck at three. That works. Kinda. I&amp;#39;d much rather default to laying out a list into a column layout based on the available space.&lt;/p&gt;
&lt;p&gt;Container queries rule:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:has(&amp;gt; :where(ol, ul):not(:where([class],[style])) :where(li:nth-child(12))) {
	container-type: inline-size;
}

:where(ol, ul):not(:where([class],[style])) {
	columns: var(--columnsCount, 1);
}

@container (min-width: 400px) {
	:where(ol, ul):not(:where([class],[style])) {
		--columnsCount: 2;
	}
}

@container (min-width: 600px) {
	:where(ol, ul):not(:where([class],[style])) {
		--columnsCount: 3;
	}
}&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;This uses &lt;code&gt;:has()&lt;/code&gt; to select any direct ancestor of an assumed-to-be unstyled list that contains at least 12 &lt;code&gt;li&lt;/code&gt; children, making it &amp;quot;a query container for dimensional queries on the inline axis of the container.&amp;quot; (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/container-type#:~:text=Establishes%20a%20query%20container%20for%20dimensional%20queries%20on%20the%20inline%20axis%20of%20the%20container.&quot;&gt;MDN&lt;/a&gt;. Throwing some arbitrary widths on there, the unstyled lists will have one column for every 200 pixels of its direct ancestor&amp;#39;s width.&lt;/p&gt;
&lt;p&gt;Worth pointing out that the &lt;code&gt;:nth-child(12)&lt;/code&gt; psuedo-class is only used on the direct ancestor selector because if the container is not defined (&lt;code&gt;container-type&lt;/code&gt;), the queries fail, and the double-negative defaults to a single column list thanks to the undefined custom property, &lt;code&gt;--columnsCount&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Adding &lt;code&gt;:has()&lt;/code&gt; into the equation does not add any specificity. The net-zero specificity comes in handy because you can use any ancestor and create a container to override the value of &lt;code&gt;--columnsCount&lt;/code&gt;, and any selector will work without forcing it or creating an overly specific selector.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;But where is the curse?&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Look at the direct ancestor selector. Look at it:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:has(&amp;gt; :where(ol, ul):not(:where([class],[style])) :where(li:nth-child(12)))&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Look at it and tell me anything makes sense anymore.&lt;/p&gt;
&lt;p&gt;In a single line, using only type and pseudo-class selectors, without producing a bit of specificity, without needing to add anything but the HTML, I can select the parent of an element which contains at least 12 &lt;code&gt;li&lt;/code&gt; children and which, to the best of my knowledge, has no previously applied styles.&lt;/p&gt;
&lt;p&gt;This is a selector that should not be written. It is a threat to the church.&lt;/p&gt;
&lt;p&gt;I have been mulling this over for a long time, reworking my answer as new modules land in browsers. Been a lot of fun. Now that we have container queries in all browsers, this cursed CSS is ready for production. You can see it in use in the list of constraints at the start of this post.&lt;/p&gt;
&lt;p&gt;Unabashedly, this is a prime example of my &amp;quot;I want anyone who sees this to think, wow, he knows stuff&amp;quot; code. Plain overwrought. There is literally an easier solution, one most people can look at and say &amp;quot;Yes, I know what is going on.&amp;quot; Instead, the longer you gaze at this selector, the longer it gazes back at you.&lt;/p&gt;
&lt;p&gt;In certain circumstances, sure, it could be useful. Like I&amp;#39;m using it here, I think there could be a use for this in handling a CMS&amp;#39; opinionated output. But there are better solutions, something other people can work with and understand: a scoping selector &lt;code&gt;.blog-post :where(ul,ol)&lt;/code&gt; would work just fine.&lt;/p&gt;
&lt;p&gt;Just do that.&lt;/p&gt;
&lt;p&gt;Or don&amp;#39;t. I&amp;#39;m not the boss of you.&lt;/p&gt;
&lt;p&gt;Use the double-negative selector everywhere.&lt;/p&gt;
&lt;p&gt;Never think about the double-negative selector again.&lt;/p&gt;
&lt;p&gt;Write a rebuttal.&lt;/p&gt;
&lt;p&gt;Write your own cursed CSS.&lt;/p&gt;
&lt;p&gt;Write a rebuttal that includes your own cursed CSS.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>LOLDNS</title>
				<link href="https://www.tattooed.dev/wrote/loldns/"/>
				<updated>2023-01-20T03:33:00Z</updated>
				<id>urn:uuid:c837b25c-b228-4333-a999-998b0a905f0f</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;There is some lengthy backstory that brings us here, but believe me, it&amp;#39;s boring. And also believe me when I tell you that I needed to set up some MX records on my old domain--&lt;code&gt;vincefalconi.com&lt;/code&gt;--but when I went to add the domain in DigitalOcean, it told me the entry already existed. Three times I looked at my tiny list of two domains in my DNS panel, convinced I was overlooking the thing that plainly was not there.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Listen, you and I both know you know what I did. But you and I both know why we&amp;#39;re here. So I&amp;#39;m going to keep writing and you&amp;#39;re going to keep reading.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;I opened the domain in a tab. Got a warning that the TLS cert was bad and assumed that was because it was somehow getting this site&amp;#39;s cert by mistake. No. No no no.&lt;/p&gt;
&lt;p&gt;The domain was redirecting to a fake landing page for a well-known software product and linking to what I assume was malware. Don&amp;#39;t click weird links. And if you do only do it once. I&amp;#39;m not a role model.&lt;/p&gt;
&lt;p&gt;I did a little &lt;code&gt;dig&lt;/code&gt;-ging and saw the domain, which I still controlled, was pointing to a DigitalOcean resource I did not control.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;See, we both know. Go on.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Midway through 2019, I moved this site from &lt;code&gt;vincefalconi.com&lt;/code&gt; to this clever &lt;code&gt;tattooed.dev&lt;/code&gt; domain we&amp;#39;re meeting on today. At some point in late 2022, chasing the high of perfect Lighthouse scores, I moved my site to Netlify and thought &amp;quot;Oh, let me shutdown everything in DO, no need in running that server anymore, might as well clean up the DNS, too...&amp;quot;&lt;/p&gt;
&lt;p&gt;So I deleted the DNS records. All of &amp;#39;em.&lt;/p&gt;

							
						
							
								
	

	
		
		
			&lt;picture&gt;
				
					&lt;source srcset=&quot;https://assets.tattooed.dev/images/clue-dns.avif&quot; type=&quot;image/avif&quot; /&gt;
				
					&lt;source srcset=&quot;https://assets.tattooed.dev/images/clue-dns.webp&quot; type=&quot;image/webp&quot; /&gt;
				
				&lt;img
					class=&quot;blog-image--block-center&quot;
					src=&quot;https://assets.tattooed.dev/images/clue-dns.jpg&quot;
					width=&quot;540&quot;
					height=&quot;360&quot;
					loading=&quot;lazy&quot;
					alt=&quot;Madeline Kahn as Mrs White in the movie &amp;quot;Clue&amp;quot; declaring &amp;quot;Yes, I did it. I killed Yvette.&amp;quot; But &amp;quot;killed Yvette is marked out and replaced with &amp;quot;broke DNS.&amp;quot;&quot; /&gt;
			&lt;/picture&gt;
		
	
	

							
						
							
								&lt;p&gt;I left &lt;code&gt;vincefalconi.com&lt;/code&gt; pointing at DigitalOcean&amp;#39;s nameservers, without anything in DigitalOcean configured to respond.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re unfamiliar with DNS, I suggest checking out Julia Evans&amp;#39; wonderful explainer &lt;a href=&quot;https://wizardzines.com/&quot;&gt;zines and comics&lt;/a&gt;. Start with &lt;a href=&quot;https://wizardzines.com/comics/life-of-a-dns-query/&quot;&gt;this one&lt;/a&gt;. For our story, here are three things about DNS I would like you to understand:&lt;/p&gt;
&lt;p&gt;First, nameservers tell computers where a domain should route. If a domain is pointed at a nameserver, but the nameserver isn&amp;#39;t configured to respond to requests for that domain, nothing happens until someone tells it what to do by way of DNS records. There&amp;#39;s nuance, but that&amp;#39;s the gist of it.&lt;/p&gt;
&lt;p&gt;Second, generally speaking, when using a hosted DNS service like DigitalOcean&amp;#39;s, your DNS records are not editable by others but they are lumped together with everyone else&amp;#39;s records because their nameservers are not account-specific. Everyone uses ns1.digitalocean.com, etc; you do not get vinces-awesome-DO-account-ns1.digitalocean.com, etc.&lt;/p&gt;
&lt;p&gt;Third, you can add records for any domain you want, even if you don&amp;#39;t own it. Nothing happens unless someone points their domain at your nameserver, and DNS has no reason and no reliable method of ensuring the author of a record is in control of the domain it&amp;#39;s routing.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Combine these three ideas and gasp with me, won&amp;#39;t you?&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Someone found my old domain was still pointed at DigitalOcean but returned no DNS records, so they added one for it and that&amp;#39;s how you get malware. If you clicked the link. Which I didn&amp;#39;t do. And neither did you. Because we are making good choices for ourselves.&lt;/p&gt;
&lt;p&gt;DigitalOcean was great to work with to get control of the DNS records. I didn&amp;#39;t think I&amp;#39;d hear back because my account is pennies by comparison, but they had it resolved same day. This is not sponcon.&lt;/p&gt;
&lt;p&gt;Do you have a bunch of domains just sitting somewhere, not doing anything? It&amp;#39;s probably a good idea to take a few minutes and make sure they aren&amp;#39;t pointed somewhere you don&amp;#39;t control. And if the domain is truly inactive, I don&amp;#39;t see an issue with just removing the nameserver entries for it entirely, but our situations are not the same.&lt;/p&gt;
&lt;p&gt;If you find that your domain&amp;#39;s DNS records have been hijacked, my humble recommendation is to report it to whatever service is hosting the records and follow their guidance. I wasn&amp;#39;t worried about breaking anything because &lt;code&gt;vincefalconi.com&lt;/code&gt; was low traffic even before I stopped using it, so I changed the domain&amp;#39;s settings in my registrar while I waited on DO to intervene. You may not have that luxury, so it&amp;#39;s best to work with the people and the service closest to the records.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Weeknotes for watching cloudy skies</title>
				<link href="https://www.tattooed.dev/wrote/weeknotes-for-watching-cloudy-skies/"/>
				<updated>2023-01-08T06:00:00Z</updated>
				<id>urn:uuid:847a5624-c797-4f87-9a1a-4e28cb246b7f</id>
				<content type="html">
					
					
						
							
								&lt;ul&gt;
&lt;li&gt;Moved (nearly) all of my web presence back to DigitalOcean after bouncing around Netlify and AWS for a few months. Netlify was not bad, but something about its process never clicked for me, and AWS was, well, AWS. My humble site doesn&amp;#39;t get much traffic, but the main thing that turned me off of AWS was the constant fear that if I didn&amp;#39;t account for some variable, I could be on the hook for hundreds of dollars out of the blue. I took stock of what I had to build to resettle into DO and couldn&amp;#39;t help but laugh. I thought it would be as easy as setting up a static site app in DO&amp;#39;s App Platform, but everyone&amp;#39;s friend, DNS, made that an issue. Sparing the details that I&amp;#39;ll eventually put in their own post, my simple static site app with a storage Space also has a load balancer and a small droplet in the equation. Feels like a bit of complexity, but hey, it makes sense to me and it works.&lt;/li&gt;
&lt;li&gt;I installed a &lt;a href=&quot;https://weatherflow.com/tempest-weather-system/&quot;&gt;weather monitor&lt;/a&gt; in my backyard. This started as &lt;a href=&quot;https://hachyderm.io/@papayaga/109514457892977343&quot;&gt;a joke&lt;/a&gt; because &lt;a href=&quot;https://hachyderm.io/@papayaga/109514400129004936&quot;&gt;I wanted to know how much rain we&amp;#39;d received&lt;/a&gt; in the middle of a gnarly storm. That joke turned into building a tool to scrape data from APIs from the National Weather Service and NOAA (coming soon to a blog post near you), which served as both a proof-of-concept and an official context for any measurements I might make on my own. The monitor&amp;#39;s been up all weekend, and like a gift from the universe, we got a thunderstorm last night that let me see the lightning strike detection in action. I plan on using this for gardening, as if I need a practical excuse to do anything, but more importantly, this has been a satisfying learning process. Next up on the agenda? Wrangling the station&amp;#39;s UDP broadcast, a protocol I&amp;#39;ve never worked with before.&lt;/li&gt;
&lt;li&gt;I decided not to do an end-of-year post, but I did take a look at what I&amp;#39;ve done this last year. I wrote more on this site than I ever have, with half of those posts happening after deciding to exit Twitter. I didn&amp;#39;t watch more movies than ever before, but I did &lt;a href=&quot;/bookshelf/&quot;&gt;finish more books&lt;/a&gt;. That feels great. It&amp;#39;s hard to compare any of the books I finished because they&amp;#39;re all so different, but I&amp;#39;d be lying if I said that my favorite wasn&amp;#39;t Kiese Laymon&amp;#39;s &amp;quot;Heavy,&amp;quot; half because it is fantastic on its own and half because Laymon is from Jackson. Finished my first book of the new year today, Nikole Hannah-Jones&amp;#39; &amp;quot;The 1619 Project.&amp;quot; What a powerful work.&lt;/li&gt;
&lt;li&gt;To celebrate the end of &lt;a href=&quot;https://mississippitoday.org/jackson-water-crisis/&quot;&gt;the most recent city-wide boil water notice&lt;/a&gt;, I had breakfast at one of &lt;a href=&quot;https://www.urbanfoxesjxn.com/&quot;&gt;my favorite local spots&lt;/a&gt; with a friend yesterday. I&amp;#39;ve been outside plenty over the last few years, but leaning back into my chair on that patio, it felt like the first time I&amp;#39;ve been able to actually see a blue sky in a long time. There were a few clouds, but that blue was undeniable. Here&amp;#39;s to hoping more of that happens this year.&lt;/li&gt;
&lt;/ul&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Using CloudFront to host your Mastodon handle on your own domain</title>
				<link href="https://www.tattooed.dev/wrote/using-cloudfront-to-host-your-mastodon-handle-on-your-own-domain/"/>
				<updated>2022-11-19T03:51:00Z</updated>
				<id>urn:uuid:c01a02ca-26bb-4820-ad4b-5f6bb1e70569</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;The plan was to host my own private instance, maybe invite some Twitter mutuals who want out. Ran into enough issues with setting up the server that I just bailed and signed up elsewhere.&lt;/p&gt;
&lt;p&gt;I still want to be able to tell someone to look me up using my own domain, not the instance&amp;#39;s domain&amp;mdash;another way to own my URL somewhat. &lt;a href=&quot;https://blog.maartenballiauw.be/post/2022/11/05/mastodon-own-donain-without-hosting-server.html&quot;&gt;Maarten Balliauw&amp;#39;s blog post&lt;/a&gt; explains how to do just that:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;In other words, if you want to be discovered on Mastodon using your own domain, you can do so by copying the contents of &lt;code&gt;https://&amp;lt;your mastodon server&amp;gt;/.well-known/webfinger?resource=acct:&amp;lt;your account&amp;gt;@&amp;lt;your mastodon server&amp;gt;&lt;/code&gt; to &lt;code&gt;https://&amp;lt;your domain&amp;gt;/.well-known/webfinger&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One caveat: this approach works much like a catch-all e-mail address. &lt;code&gt;@anything@yourdomain.com&lt;/code&gt; will match, unless you add a bit more scripting to only show a result for resources you want to be discoverable.&lt;/p&gt;

		&lt;/blockquote&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;It&amp;#39;s pretty easy to just copy-paste the webfinger file from your host to your website, but the note about it working the same as a catch-all email address bothered me. I didn&amp;#39;t like the idea of any handle on this domain pointing to my Mastodon profile. Too easy troll material.&lt;/p&gt;
&lt;p&gt;I host this site in S3 behind CloudFront, so I decided to add &lt;a href=&quot;https://github.com/vfalconi/cloudfront-finger&quot;&gt;a CloudFront function&lt;/a&gt; that uses the same query string as Mastodon&amp;#39;s webfinger endpoint. This lets me return the correct account for the handle, or return a &lt;code&gt;404&lt;/code&gt; if no account is found. The key here is publish the function, and use it on the path &lt;code&gt;/.well-known/webfinger&lt;/code&gt; in your CloudFront distribution&amp;#39;s Behaviors tab.&lt;/p&gt;
&lt;p&gt;Real nice how this works. This fediverse business is pretty neat.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Some thoughts at the end of the timeline</title>
				<link href="https://www.tattooed.dev/wrote/some-thoughts-at-the-end-of-the-timeline/"/>
				<updated>2022-11-18T04:30:00Z</updated>
				<id>urn:uuid:d2bbae4c-9945-44dc-b74d-69e6e50f87de</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;I signed up for Twitter in February 2007&amp;mdash;its first year&amp;mdash;after reading about the app throughout the last fall semester before I left college. I don&amp;#39;t think I qualified as an Early Adopter since I wasn&amp;#39;t on at launch, but I&amp;#39;ve seen the site&amp;#39;s journey from the original slime-saturated Twttr logo to the Muskian dumpster fire today.&lt;/p&gt;
&lt;p&gt;Like others in the bird app cohort, I&amp;#39;m collecting my thoughts about what&amp;#39;s happening. And it is not lost on me that, like so many others, I&amp;#39;m actively doing that thought-collecting off of the platform itself.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#building-go-boom&quot;&gt;I remembered something&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#our-prediction-math-has-no-power-here&quot;&gt;There&amp;#39;s no telling when it&amp;#39;s all going to blow up&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mastodon&quot;&gt;Mastodon, maybe, but probably not&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-web-is-dead-long-live-the-web&quot;&gt;I&amp;#39;m getting back into the indie web&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;building-go-boom&quot;&gt;Building go boom&lt;/h2&gt;
&lt;p&gt;One summer when I was a kid, a cousin took my brother and me down the street to watch an old steel mill get imploded. Were we watching a symbol of a dying industry getting wiped off of the map? I don&amp;#39;t know, we were there to watch a building go boom. Using Twitter the last two weeks has this same energy: half of me is watching infrastructure collapse while the other half is laughing at the implosion.&lt;/p&gt;
&lt;p&gt;The difference here is that this implosion, while every bit intentional, is not nearly as well planned. With a building, you have to worry about debris large and small and the area of effect as it were. There are defined processes for this sort of thing, after all. Meanwhile, Twitter is just a web site. What damage could be done?&lt;/p&gt;
&lt;h2 id=&quot;our-prediction-math-has-no-power-here&quot;&gt;Our prediction math has no power here&lt;/h2&gt;
&lt;p&gt;The Billionaire Baby&amp;#39;s public plan was firing a majority of people on staff guarantees the decline of the platform. The loss of knowledge is too great, whether you become a grindhouse, you will crash.&lt;/p&gt;
&lt;p&gt;I argue that there is no point in trying to call the time on the Last Major Outage. No amount of domain expertise can account for Elon Musk&amp;#39;s erratic decision-making. Working for a person with no plan but a lot of pressure means you cannot meet their expectations or anticipate their demands.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;But also, six months. I think the sun will rise on May 1, 2023, and Twitter will be gone.&lt;/del&gt; As I was finishing this up, &lt;a href=&quot;https://www.theverge.com/2022/11/17/23465274/hundreds-of-twitter-employees-resign-from-elon-musk-hardcore-deadline&quot;&gt;news broke&lt;/a&gt; that, after Musk presented a &amp;quot;work longer hours for same pay, mandatory in-person work, and you cannot make fun of me&amp;quot; ultimatum to 3,000 employees, 75% of them declined the offer. Baby we are in endgame! The World Cup is here, and the remaining engineers will be ground into pulp to keep the site running through the outages the games bring. Outlook is not great past that.&lt;/p&gt;
&lt;h2 id=&quot;mastodon&quot;&gt;Mastodon&lt;/h2&gt;
&lt;p&gt;I&amp;#39;m on Mastodon. It feels like trying out a new coffee shop or bar, components are familiar but the overall space is foreign. The app feels a lot like Twitter circa 2008-2010, but the experience itself is not that. I like the federated design, it feels like the old web. So we&amp;#39;ll see how it goes.&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;Twitter’s significance is not about revenue or advertising platforms or new features. It is about communities that create ideas. The real Twitter lives in the practices of people who can migrate at any time. User migration and social fragmentation are the real present threat to Twitter’s cultural dominance.&lt;/p&gt;

		&lt;/blockquote&gt;
		
			&lt;figcaption class=&quot;quote__caption&quot;&gt;
				&lt;p&gt;Tressie McMillan Cottom&lt;/p&gt;

				
					&lt;cite class=&quot;quote__cite&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://www.nytimes.com/2022/05/03/opinion/the-real-twitter-is-not-for-sale.html&quot;&gt;Black Twitter Is Not a Place. It’s a Practice.&lt;/a&gt;&lt;/p&gt;
&lt;/cite&gt;
				
			&lt;/figcaption&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;I think Tressie nails the secret behind Twitter, and its existential threat.&lt;/p&gt;
&lt;p&gt;Despite my appreciation for its philosophy and my respect for its implementation, Mastodon is not part of that existential threat. I am happy to be wrong, but I doubt I&amp;#39;ll ever know that joy. There&amp;#39;s too much overhead before you even begin to sign up. Before even signing up, new users are met with friction both from the concept of federation and the burden of choosing a server. Choosing a username is hard enough.&lt;/p&gt;
&lt;p&gt;But more than that, Mastodon lacks the established communities of Twitter. Does it have its own? Sure. Are some communities migrating to its fediverse? Also sure. Do either of these things prove me wrong? 🙃&lt;/p&gt;
&lt;p&gt;First, there is a baseline of exhaustion from the social web. That is true of all the platforms, but I think there is something special about Twitter users: we don&amp;#39;t give up. We stay on the app, no matter what. We know it&amp;#39;s bad for us. We know this, we know this, we know this, and we stay. On the other hand, Mastodon is a lot. And the more people who find out it&amp;#39;s a lot, the more people talk about how Mastodon is a lot.&lt;/p&gt;
&lt;p&gt;Without a base of casual users to consume their content, you find the second group who won&amp;#39;t migrate: Twitter power users. If they don&amp;#39;t have a built-in audience, I don&amp;#39;t think they have a reason to move. This group includes the influencers and social media strategists warning about pouring resources into establishing a Mastodon presence until they know if their audience is moving, too. Makes sense.&lt;/p&gt;
&lt;p&gt;There is an overlap between the first and second groups: the Twitter power user who is going to move to an existing platform, they just haven&amp;#39;t decided which, but who is at a level of tired that precludes them from signing up for something new. This is our third group. This is the jaded Twitter power user. They may have hundreds or they may have hundreds of thousands of followers. Twitter has worn them down, but their other platforms are lighter, more fun. We are Twitter, we change you.&lt;/p&gt;
&lt;p&gt;We break you.&lt;/p&gt;
&lt;p&gt;The jaded Twitter power user isn&amp;#39;t going to pick up Mastodon, not because it&amp;#39;s a lot, but because it&amp;#39;s ultimately unnecessary. They will migrate by path of least resistance. This group knows the power and the benefit of the social web, but they also understand the ephemeral nature of these platforms. Why invest in the new Peach, or the new Ello?&lt;/p&gt;
&lt;p&gt;Without these three groups, Mastodon does not reach either the literal or the influential scale that Twitter achieved, even at its lowest. This is not a bad thing. Mastodon does not have to be a 1:1 replacement to be great in its own right.&lt;/p&gt;
&lt;h2 id=&quot;the-web-is-dead-long-live-the-web&quot;&gt;The web is dead, long live the web&lt;/h2&gt;
&lt;p&gt;The social web isn&amp;#39;t going to die when Twitter dies. Twitter is famously used by fewer people compared to its impact on news, on politics, on culture in general. Its collapse will have different kinds of effects, but it won&amp;#39;t weaken the social industry over all. The users will move and shift, maybe a viable replacement pops up since there are a lot of subject matter experts on the market.&lt;/p&gt;
&lt;p&gt;For me, this is it. I nuked both Facebook and Instagram. I&amp;#39;m read-only on TikTok and a general lurker on Reddit. Feels like I will be offline more&amp;mdash;always a good thing&amp;mdash;and when I&amp;#39;m online, I&amp;#39;ll spend more time on &lt;a href=&quot;https://indieweb.org/&quot;&gt;the indie web&lt;/a&gt;. RSS feeds. Web mentions. Mastodon for a little town commons time. The DIY indie web fits in great with the hardware and networking tinkering I do for fun. This makes me hopeful the web can be a fun place again.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s sustainable. That seems like a lot by comparison, but it&amp;#39;s a level of attention-investment I can live with because, from where I sit, there&amp;#39;s a built-in limit. The scale of the indie web is inherently limited to what you want vs what an algorithm chooses for you.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>How to Find Something</title>
				<link href="https://www.tattooed.dev/wrote/how-to-find-something/"/>
				<updated>2022-11-13T23:26:00Z</updated>
				<id>urn:uuid:cce9d699-26b3-414c-82df-2472bd8a0a66</id>
				<content type="html">
					
					
						
							
								&lt;ol&gt;
&lt;li&gt;Have something you value&lt;/li&gt;
&lt;li&gt;Misplace (1)&lt;/li&gt;
&lt;li&gt;Look everywhere for (1)&lt;/li&gt;
&lt;li&gt;Accept that (1) is gone&lt;/li&gt;
&lt;li&gt;Look again, just to be sure&lt;/li&gt;
&lt;li&gt;No, it&amp;#39;s gone, it&amp;#39;s time to move on&lt;/li&gt;
&lt;li&gt;Allow time to pass&lt;/li&gt;
&lt;li&gt;Tidy up a space&lt;/li&gt;
&lt;li&gt;Uncover a hint of (1), but refuse to get your hopes up, there&amp;#39;s no way it&amp;#39;s right here, you looked here, it wasn&amp;#39;t here, you&amp;#39;re getting your hopes up, it&amp;#39;s not here&lt;/li&gt;
&lt;li&gt;Find (1)&lt;/li&gt;
&lt;/ol&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Guerrilla Public Service Project: MPB Radio Schedule</title>
				<link href="https://www.tattooed.dev/wrote/guerilla-public-service-project-mpb-radio-schedule/"/>
				<updated>2022-11-07T03:18:00Z</updated>
				<id>urn:uuid:19394795-a92d-4c85-88f4-49f1e135f3f7</id>
				<content type="html">
					
					
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;And mostly, when we see these things, we grumble on the inside and then do nothing. There are all sorts of reasons for our inertia. We don’t know how to fix it. It’s not ours to fix. We could get in trouble. You might notice these little design flaws for years, silently fuming, until one day.&lt;/p&gt;

		&lt;/blockquote&gt;
		
			&lt;figcaption class=&quot;quote__caption&quot;&gt;
				&lt;p&gt;David Weinberg&lt;/p&gt;

				
					&lt;cite class=&quot;quote__cite&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://99percentinvisible.org/episode/guerrilla-public-service&quot;&gt;99 Percent Invisible: Guerrilla Public Service Redux&lt;/a&gt;&lt;/p&gt;
&lt;/cite&gt;
				
			&lt;/figcaption&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Before the pandemic, my consumption of public radio was NPR in the car on the way to the office. When that drive disappeared, NPR ended up playing an even bigger role in my routine--starting my day scrambling eggs and along to 1 A and winding down for the night with Fresh Air. The whole time, though, I was using my XM subscription, overlooking MPB, my local station. Truthfully, it wasn&amp;#39;t until a friend said he was calling into &lt;a href=&quot;http://gestaltgardener.mpbonline.org/&quot;&gt;Felder Rushing&amp;#39;s Gestalt Gardener&lt;/a&gt; in the last couple of months that I even looked at &lt;a href=&quot;(https://www.mpbonline.org/radio/schedule/)&quot;&gt;their online schedule&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That schedule is my personal version of &lt;a href=&quot;https://99percentinvisible.org/episode/guerrilla-public-service/#attachment_5166&quot;&gt;Richard Ankrom&amp;#39;s 110 freeway sign&lt;/a&gt; highlighted in 99 Percent Invisible. Ankrom missed his turn on 110 in LA once because there was no sign indicating when to turn off. He was a trained sign painter, and after a few years of stewing on the inadequate signage, he decided to change add a turn-off indicator on his own. The MPB schedule never made me late for work, but that schedule has irked me every time I&amp;#39;ve used it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;#39;s unwieldy. No matter the viewport, I find it difficult to know the time for a show the farther right in the table the column is.&lt;/li&gt;
&lt;li&gt;It shows  me a lot of information I don&amp;#39;t need. No matter what day it is, I see all of the programming for that week. So on Friday, I&amp;#39;m still seeing the past Monday&amp;#39;s lineup.&lt;/li&gt;
&lt;li&gt;It isn&amp;#39;t even linked on the &lt;a href=&quot;https://www.mpbonline.org/radio/&quot;&gt;MPB Radio landing page&lt;/a&gt;, which tells me it&amp;#39;s likely not anyone&amp;#39;s worry. The landing page &lt;em&gt;does&lt;/em&gt; have an embedded player on it that tells you what&amp;#39;s on the air by default, but nothing about what&amp;#39;s coming up. But that player is not why we&amp;#39;re here today.&lt;/li&gt;
&lt;li&gt;It not being linked on the landing page means I had to use Google to get to it, which meant I occasionally had to think about which search result was the right one. This is a serious inconvenience. And I will not take questions about why I didn&amp;#39;t just bookmark the correct page.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;#39;s just not helpful to me. I don&amp;#39;t want to say it, but reader, I had my sign.&lt;/p&gt;
&lt;p&gt;I wanted a version of this schedule that showed me up front what was currently on the air. Then what was coming up, then what was happening in the next days. With my hunch about the page being orphaned, and not wanting to be That Guy who demanded to know why something someone else made didn&amp;#39;t meet my extremely specific requirements, I started thinking about what I would need to build my own.&lt;/p&gt;
&lt;p&gt;First, I&amp;#39;d need a data source. There&amp;#39;s no point in manually maintaining the station&amp;#39;s schedule, because that could change or it could just be me verifying that, yep, it&amp;#39;s the same this week again. Not a good use of my time. But looking at the table, there are buttons to move to older or upcoming schedules, too, which told me there was a feed of some kind somewhere. Taking the URL from the network tab in dev tools, I fiddled with the query string until I got a week&amp;#39;s schedule in JSON. Had my data source.&lt;/p&gt;
&lt;p&gt;Second, I&amp;#39;d need a way to render the JSON. You might think I&amp;#39;d go for a single-page application. But the relevant information changes every 30 minutes, that isn&amp;#39;t a screaming use-case for something like Vue or even a web component at this point, in my opinion. It is, however, a definite use-case for my favorite static-site generator, &lt;a href=&quot;https://www.11ty.dev&quot;&gt;Eleventy&lt;/a&gt;. Next!&lt;/p&gt;
&lt;p&gt;If I&amp;#39;m not going to use client-side code to update the page, I&amp;#39;ll need a way to republish the page on a schedule. This was where I fought my inner need for complexity, but do not think this is a hero&amp;#39;s story. I looked at DigitalOcean Functions, CircleCI, and GitHub Actions. They all did more or less the same thing--read jobs written in YAML, take some inputs, run some code, push some output--but just not what I needed.&lt;/p&gt;
&lt;p&gt;Primarily, I needed a job to run on a set schedule and push files to a static site. DO Functions are in beta, and has some limitations around S3-based hosting, so nope. CircleCI and GitHub Actions are wonderful, but their scheduling isn&amp;#39;t what I want--Circle flat out says that a cron schedule of &amp;quot;every thirty minutes&amp;quot; will actually run twice an hour, and GitHub&amp;#39;s documentation claims to hold to the cron schedule in your workflow, but it too cannot guarantee an exact run time. And this totally makes sense, everyone probably defaults to wanting their jobs run on the hour which expands their workload and would grind their hardware to a halt.&lt;/p&gt;
&lt;p&gt;This left me exactly where I said I didn&amp;#39;t want to be, staring a man named Jenkins in the face. I was reluctant to self-host Jenkins because in my heart I knew there&amp;#39;d be some catch, some config setting I couldn&amp;#39;t get right, but no. I used Portainer&amp;#39;s one-click installer, setup some DNS, and it just worked. And better than that, I was able to negotiate with its GUI to get my scheduled build happening on the half hour&lt;sup&gt;&lt;a href=&quot;#note1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;With the build tool in place, I wrote a little deployment script or three, and my guerrilla public service project lives: &lt;a href=&quot;https://tattooed.dev/made/whats-on-mpb-radio&quot;&gt;What&amp;#39;s on MPB Radio&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The thing about guerrilla public service, as 99PI talks about, is that it&amp;#39;s often met with resistance from Actual Public Service people. In the case of Ankrom&amp;#39;s sign, there were serious public safety concerns, but with my little web page, I&amp;#39;m not misdirecting people or potentially dropping large pieces of metal on to passing cars.&lt;/p&gt;
&lt;p&gt;Just makin&amp;#39; stuff to make daily life a little better, hopefully.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>I :has() an idea about lists</title>
				<link href="https://www.tattooed.dev/wrote/i-has-an-idea-about-lists/"/>
				<updated>2022-09-21T20:09:00Z</updated>
				<id>urn:uuid:c13519c9-a7c2-4935-8874-2d8e0d66ca99</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;While I was rewriting this site&amp;#39;s CSS, I wanted to find something that could benefit from the new &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:has&quot;&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/a&gt; pseudo-class, without inventing a need for it.&lt;/p&gt;
&lt;p&gt;Friend, that something is lists.&lt;/p&gt;
&lt;p&gt;I love lists. Ordered, unordered, definition, I don&amp;#39;t care, you have a set of things, you need a list. One thing that&amp;#39;s bothered me about lists--specifically of the un/ordered variety, is that they can become pretty long and scroll-y.&lt;/p&gt;
&lt;p&gt;And with &lt;code&gt;:has()&lt;/code&gt;, we can wrangle those lengthy lists pretty easily.&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.smarter-list {
  columns: var(--listColumns);
}

.smarter-list:has(:nth-child(n+21)) {
  --listColumns: 2;
}

.smarter-list:has(:nth-child(n+41)) {
  --listColumns: 3;
}&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;What&amp;#39;s going on here? First, any &lt;code&gt;.smarter-list&lt;/code&gt; should use columns for its layout. Great, that&amp;#39;s reasonable, but how many? By default, none, as &lt;code&gt;--listColumns&lt;/code&gt; is undefined without a default, and the browser moves on with its life. But if the &lt;code&gt;.smarter-list&lt;/code&gt; has twenty-one child elements, it should use a two-column layout. Why twenty-one? Because I arbitrarily chose twenty as my per-column limit, so the presence of a twenty-first child covers children 21-40. And for more than forty? Double the limit, add one, and boom, three columns.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/vf_/pen/KKRvvXG&quot;&gt;Here&amp;#39;s a CodePen&lt;/a&gt; if that&amp;#39;s helpful.&lt;/p&gt;
&lt;p&gt;I think this pattern is helpful because it keeps the number of classes in my markup to a minimum, and it doesn&amp;#39;t require processing in a template to determine which classes to apply based on how many items are in a given list. For this site specifically, I&amp;#39;d have to either do a RegEx test on the raw Markdown in my posts, or do a similar test on the compiled HTML. Put simply, it&amp;#39;s overhead I don&amp;#39;t want to deal with.&lt;/p&gt;
&lt;p&gt;Now, &lt;a href=&quot;https://caniuse.com/css-has&quot;&gt;&lt;code&gt;:has()&lt;/code&gt; does not have universal support yet&lt;/a&gt;, so what&amp;#39;s a front-end maker like yourself supposed to do about that? CSS Grid can do the same thing, but with slightly more complex selectors.&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.smarter-list {
  display: grid;
  grid-template-columns: repeat(3, auto);
  grid-auto-flow: column;
}

.smarter-list :nth-child(n+1) ~ * {
  grid-column: 1;
}

.smarter-list :nth-child(n+20) ~ * {
  grid-column: 2;
}

.smarter-list :nth-child(n+40) ~ * {
  grid-column: 3;
}&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;This works much the same way as the &lt;code&gt;:has()&lt;/code&gt; method, except that the column assignment is happening &amp;quot;manually&amp;quot; on the sibling elements of the twentieth and fortieth &lt;code&gt;.smarter-list&lt;/code&gt; children. This boils down to the difference between CSS Grid and CSS Columns--along with Floats and Flexbox, a selection of layout tools in our kits, equal in their value and beauty.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/vf_/pen/eYrEEwm&quot;&gt;Here&amp;#39;s another CodePen&lt;/a&gt; in these trying times.&lt;/p&gt;
&lt;p&gt;For me and my strong opinions tightly held, I prefer to use single-class selectors to keep specificity low, and by my math, both methods here result in the same specificity (20), but using &lt;code&gt;:has()&lt;/code&gt; also lets me keep nesting out of my selectors. &lt;/p&gt;
&lt;p&gt;My ideal CSS selector is a single class, with psuedo-classes/elements added as necessary. I like to avoid nesting in general, because I like to keep my specificity as low as possible. By my math, both &lt;code&gt;.smarter-lists&lt;/code&gt; methods result in the same specificity (20). I think I prefer the &lt;code&gt;:has()&lt;/code&gt; method as it lets me live my unnested fantasy, and ultimately require less code. But also? C&amp;#39;mon, it&amp;#39;s the new and shiny!&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Hex the Moon</title>
				<link href="https://www.tattooed.dev/wrote/hex-the-moon/"/>
				<updated>2022-09-16T04:12:00Z</updated>
				<id>urn:uuid:88f56b4c-3a1f-48a9-be62-6845e74cb916</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;The favicon on this humble site has been broken for a while now. This site is work in progress at all times, so I&amp;#39;ve let it linger. But looking at it tonight, I saw I&amp;#39;d mistaken actual words for a chain icon. &amp;quot;Undefined.&amp;quot; Now, who am I to turn away from some debugging--but that&amp;#39;s not why we&amp;#39;re here.[1] It&amp;#39;s been so long since I wrote this script, I&amp;#39;d forgotten how the thing works. So, join me in the righteous act of documentation and the thankless task of controlling the moon, won&amp;#39;t you?&lt;/p&gt;
&lt;p&gt;I&amp;#39;d made the landscape on this early in 2021, and by fall, I decided it would be fun to change the phase of the moon on the fly. Enter &lt;a href=&quot;https://github.com/mourner/suncalc&quot;&gt;SunCalc&lt;/a&gt;, handy library to get the phase of the moon, among other things, based on the date:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const now = new Date();
const moon = sunCalc.getMoonIllumination(now);&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Using SunCalc&amp;#39;s phase-to-words chart, I needed a set of phases, which consisted of a limit or range of phase percentages, and a string that describes the phase:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const phases = [
	{ limit: 0, state: &amp;#39;new&amp;#39; },
	{ limit: [
		0.000000000000001,
		0.249999999999999,
	], state: &amp;#39;waxing-crescent&amp;#39; },
	{ limit: 0.25, state: &amp;#39;first-quarter&amp;#39; },
	{ limit: [
		0.250000000000001,
		0.499999999999999,
	 ], state: &amp;#39;waxing-gibbous&amp;#39; },
	{ limit: 0.5, state: &amp;#39;full-moon&amp;#39; },
	{ limit: [
		0.500000000000001,
		0.749999999999999,
	 ], state: &amp;#39;waning-gibbous&amp;#39; },
	{ limit: 0.75, state: &amp;#39;last-quarter&amp;#39; },
	{ limit: [
		0.750000000000001,
		0.999999999999999,
	 ], state: &amp;#39;waning-crescent&amp;#39; },
];&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Then I walk through those &lt;code&gt;phases&lt;/code&gt;, asking if today&amp;#39;s moon looks familiar. If it does, we have found the state of the moon.&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;phases.forEach(phase =&amp;gt; {
	if (&amp;#39;object&amp;#39; === typeof phase.limit) {
		if (moon.phase &amp;gt;= phase.limit[0] &amp;amp;&amp;amp; moon.phase &amp;lt;= phase.limit[1]) {
			moon.state = phase.state;
		}
	} else if (&amp;#39;number&amp;#39; === typeof phase.limit) {
		if (moon.phase === phase.limit) {
			moon.state = phase.state;
		}
	}
});&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Here we are. We know the state of the moon. The time has come to quietly change it both in the fav icon and the landscape on the bottom of the page.&lt;/p&gt;
&lt;p&gt;For the fav icon, we have to create an SVG data URI that contains the emoji for the moon, then drop it into the &lt;code&gt;link[rel=&amp;quot;icon&amp;quot;]&lt;/code&gt;&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const moon = new Moon();
const svgMoonPhaseMask = document.getElementById(`phase-mask--${moon.state}`);
const favIcon = document.querySelector(&amp;#39;link[rel=&amp;quot;icon&amp;quot;]&amp;#39;);
const moonEmoji = {
	&amp;#39;new&amp;#39;: &amp;#39;🌑&amp;#39;,
	&amp;#39;waxing-crescent&amp;#39;: &amp;#39;🌒&amp;#39;,
	&amp;#39;first-quarter&amp;#39;: &amp;#39;🌓&amp;#39;,
	&amp;#39;waxing-gibbous&amp;#39;: &amp;#39;🌔&amp;#39;,
	&amp;#39;full&amp;#39;: &amp;#39;🌕&amp;#39;,
	&amp;#39;waning-gibbous&amp;#39;: &amp;#39;🌖&amp;#39;,
	&amp;#39;last-quarter&amp;#39;: &amp;#39;🌗&amp;#39;,
	&amp;#39;waning-crescent&amp;#39;: &amp;#39;🌘&amp;#39;,
};
const favIconData = `data:image/svg+xml,&amp;lt;svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22&amp;gt;&amp;lt;text y=%22.9em%22 font-size=%2290%22 transform=&amp;quot;rotate(45 50 50)&amp;quot;&amp;gt;${moonEmoji[moon.state]}&amp;lt;/text&amp;gt;&amp;lt;/svg&amp;gt;`;

svgMoonPhaseMask.classList.add(`phase--active`);
favIcon.href = favIconData;&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;The CSS handles the presentation. The landscape will always show a full moon by default. In fact, if the moon&amp;#39;s phase is equal to &lt;code&gt;1&lt;/code&gt;, the script makes no change to the landscape DOM. Otherwise, it finds its corresponding &lt;code&gt;g#phase mask--...&lt;/code&gt; and adds the &lt;code&gt;phase--active&lt;/code&gt; class to bring the mask&amp;#39;s opacity up.&lt;/p&gt;
&lt;p&gt;The shapes are rough estimates, they fit the style of the landscape that way. I&amp;#39;m pretty pleased with how the moon has turned out, from drawing it by hand to a little lunar calendar.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Things I&#39;ve Said While Researching My Family Tree</title>
				<link href="https://www.tattooed.dev/wrote/things-ive-said-while-researching-my-family-tree/"/>
				<updated>2022-09-12T05:00:00Z</updated>
				<id>urn:uuid:e461c4f4-d14f-44ca-bd77-78c8bbfbbd43</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;&amp;quot;Hey! That&amp;#39;s my grandmother! Why do you have my grandmother?!&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Oh, that&amp;#39;s my great-grandfather, thank y&amp;#39;all for doing the work for me.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Hey Siri, play &amp;#39;God Save the Tsar.&amp;#39;&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Grandpap, NO!&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Ooh, yeah, ok, it&amp;#39;s the Civil War, she could have been a widow.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;...GRANDPAP WHAT DID YOU DO?!&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;This is too many names. I need to lie down.&amp;quot;&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Missing Details</title>
				<link href="https://www.tattooed.dev/wrote/missing-details/"/>
				<updated>2022-05-12T23:00:00Z</updated>
				<id>urn:uuid:929d2daf-34be-484d-9b50-656fdd008d6a</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;When I started writing some JavaScript around &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLDetailsElement/toggle_event&quot;&gt;the toggle event&lt;/a&gt; for a &lt;code&gt;details&lt;/code&gt; element, I noticed the event was firing on page load, even when &lt;code&gt;summary&lt;/code&gt; element hadn&amp;#39;t been clicked or the &lt;code&gt;open&lt;/code&gt; attribute had been changed. I thought I made a mistake and spent a bit of time debugging what could be doing that. Wrote some example code, tested it in different browsers and OSes, and the result was the same--the event fired at page load, no errors, no warnings.&lt;/p&gt;
&lt;p&gt;Naturally, this took me to one of my favorite places, &lt;a href=&quot;https://html.spec.whatwg.org/multipage/interactive-elements.html#the-details-element&quot;&gt;THE HTML SPECIFICATION&lt;/a&gt;, specifically the section on how browsers should handle changes to the &lt;code&gt;open&lt;/code&gt; attribute:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;Whenever the open attribute is added to or removed from a details element, the user agent must queue an element task [that ends with the user agent firing] an event named &lt;code&gt;toggle&lt;/code&gt; at the &lt;code&gt;details&lt;/code&gt; element.&lt;/p&gt;

		&lt;/blockquote&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Yeah, that makes total sense. But what about page load? It&amp;#39;s not mentioned in the spec, at all, from what I can tell.&lt;/p&gt;
&lt;p&gt;This took me to GitHub, and let me tell you: if you&amp;#39;re searching GitHub issues for the &lt;code&gt;details&lt;/code&gt; element, you will get way more noise than signal in your results. Turns out the word &amp;quot;details&amp;quot; occurs in a lot of discussions. Who would&amp;#39;ve thought?&lt;/p&gt;
&lt;p&gt;At this point, I started to write this post (from a very different, more despair-ridden perspective) but it was late on a Friday, and I&amp;#39;m glad I gave up to do something else because today I learned that I was not imagining things! I&amp;#39;m &lt;a href=&quot;https://github.com/whatwg/html/issues/4500&quot;&gt;not the first person to notice that the spec does not mention this behavior&lt;/a&gt;:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;That doesn&amp;#39;t say what happens when the parser sets the attribute&lt;/p&gt;

		&lt;/blockquote&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;YEAH! It doesn&amp;#39;t!&lt;/p&gt;
&lt;p&gt;[taps fingers on desk]&lt;/p&gt;
&lt;p&gt;So, like, why though? That&amp;#39;s the part I&amp;#39;m still missing, and the conversation I can&amp;#39;t find. And I&amp;#39;m not sure I understand the reasoning behind &lt;code&gt;toggle&lt;/code&gt; firing when the author&amp;#39;s intent was for the element to be open, thus nothing was actually toggled. My best guess is that, because the element is a disclosure element, and the content of the element is hidden by default, so adding &lt;code&gt;open&lt;/code&gt; changes the default state of the element.&lt;/p&gt;
&lt;p&gt;Meanwhile, a &lt;code&gt;text&lt;/code&gt; &lt;code&gt;input&lt;/code&gt; can have a prefilled &lt;code&gt;value&lt;/code&gt;, a &lt;code&gt;radio&lt;/code&gt; or &lt;code&gt;checkbox&lt;/code&gt; can have the &lt;code&gt;checked&lt;/code&gt; attribute, an &lt;code&gt;option&lt;/code&gt; can have a &lt;code&gt;selected&lt;/code&gt; attribute, and none of these will fire the &lt;code&gt;change&lt;/code&gt; event at page load, even though each of those element&amp;#39;s assumed default state is to be blank, unchecked, or unselected respectively.&lt;/p&gt;
&lt;p&gt;I would love to learn more about the decision here, because in my humblest of opinions, the parser firing the &lt;code&gt;toggle&lt;/code&gt; event for any &lt;code&gt;details[open]&lt;/code&gt; element feels like a fairly big assumption on how we use the element that contradicts how other elements work. Moreover, while looking through spec issues, I saw that some want to have &lt;code&gt;toggle&lt;/code&gt; fire on other elements. So will this behavior carry over? And if so, why?&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Joshua Tree 2021</title>
				<link href="https://www.tattooed.dev/wrote/joshua-tree-2021/"/>
				<updated>2021-12-28T05:46:00Z</updated>
				<id>urn:uuid:a7b0438c-fd97-44d3-9308-4988265398e8</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;If memory serves, my phone lit up one night in October with a new message, &amp;quot;We&amp;#39;re going to Palm Springs. Meet us.&amp;quot; and then I landed in the desert on Thanksgiving night, waiting on Casey and Hayes so we could head to Joshua Tree. I don&amp;#39;t recall much happening in between. &lt;/p&gt;
&lt;p&gt;As if an elephant didn&amp;#39;t just make its grand entrance into the room, the last few years have been a bit much. I say &amp;quot;years,&amp;quot; but that doesn&amp;#39;t begin to do this time justice, does it? Everything has been on hold and uncertain and&amp;mdash;something no one seems comfortable saying, so I&amp;#39;ll step up&amp;mdash;just plain &lt;em&gt;unprecedented&lt;/em&gt;, and somehow despite pressing our pause button, it&amp;#39;s inexplicably two years later? I&amp;#39;m not a mathematician, but how.&lt;/p&gt;
&lt;p&gt;If I may tangent here, having recently revived my interest in space&amp;mdash;a natural response to sheltering in place&amp;mdash;it&amp;#39;s frustrating not to be able to call the pandemic a &amp;quot;black hole.&amp;quot; The last two years certainly feel as massive but time slows down near a collapsed star, it doesn&amp;#39;t speed up. On top of everything else, we don&amp;#39;t even get the satisfaction of a familiar metaphor. The nerve of it all.&lt;/p&gt;
&lt;p&gt;I suppose that metaphor is a lost cause, not just due to our current understanding of physics, but also considering that reconnecting with friends after a decade apart is half reminiscing and half bringing up to speed, time bending as we go. Somewhere along that curve, I came out rested and revived, but still disconnected from the me I kept trying to find, before the weight of grief multiplied. I&amp;#39;ll take that as a blessing because it means I can&amp;#39;t do a cliche and say that I went to the desert with my friends and found myself, living my 90s coming-of-age movie fantasy.&lt;/p&gt;
&lt;p&gt;That didn&amp;#39;t happen.&lt;/p&gt;
&lt;p&gt;I think the point is that I came home more present, more accepting that there isn&amp;#39;t an old me to revive.&lt;/p&gt;
&lt;p&gt;And I had a blast with my friends, but those memories are not for this.&lt;/p&gt;
&lt;p&gt;If we want to continue to ham-fist this black hole metaphor and, hey, I do, should you find yourself in a personal singularity and you aren&amp;#39;t moving through time as you&amp;#39;d like, turn yourself into Hawking radiation and escape the event horizon and return to normal speed. None of this is how it works. It&amp;#39;s fine. Everything is fine.&lt;/p&gt;
&lt;p&gt;I didn&amp;#39;t have a camera that could capture the stars I saw, but we hiked a few trails in &lt;a href=&quot;https://www.nps.gov/jotr/index.htm&quot;&gt;Joshua Tree National Park&lt;/a&gt; and I still don&amp;#39;t believe that place is real but I have &amp;quot;proof&amp;quot; &amp;quot;allegedly.&amp;quot;&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Unupdated and Unbothered</title>
				<link href="https://www.tattooed.dev/wrote/unupdated-and-unbothered/"/>
				<updated>2020-12-01T21:10:00Z</updated>
				<id>urn:uuid:b7928155-b0d6-4d3c-a374-611cb0e9d7b4</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;I pulled my RCA Lyra out of my safe with a hunch.&lt;/p&gt;
&lt;p&gt;&amp;quot;At the very least,&amp;quot; I thought, &amp;quot;the memory card should still work.&amp;quot;&lt;/p&gt;
&lt;p&gt;After plugging the Compact Flash card into my reader, Windows chimed to let me know the device was recognized and the drive was ready.&lt;/p&gt;
&lt;p&gt;There they were, the last seven MP3s I put on the player ages ago&amp;mdash;June 14, 2004, according to the last-modified dates:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&amp;quot;Better Than This&amp;quot; by Jenn Hartmann&lt;/li&gt;
&lt;li&gt;&amp;quot;Can&amp;#39;t Take My Eyes Off of You&amp;quot; by Lauryn Hill&lt;/li&gt;
&lt;li&gt;&amp;quot;Frontin&amp;#39;&amp;quot; by Pharrell&lt;/li&gt;
&lt;li&gt;&amp;quot;God of Wine&amp;quot; by Third Eye Blind&lt;/li&gt;
&lt;li&gt;&amp;quot;How&amp;#39;s It Going to Be&amp;quot; by Third Eye Blind&lt;/li&gt;
&lt;li&gt;&amp;quot;I Admit (The Boob Song)&amp;quot; by Jenn Hartmann&lt;/li&gt;
&lt;li&gt;&amp;quot;Losing a Whole Year&amp;quot; by Third Eye Blind&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I expected to cringe when I opened the drive, but no. Nothing embarrassing. Hell, I still listen to all of those songs today.&lt;/p&gt;
&lt;p&gt;Staring at the player, I thought there was no way, it&amp;#39;s 20 years old, it can&amp;#39;t work. But after putting two fresh batteries in it, the LCD display flashed the Windows Media Player logo before showing the available tracks.&lt;/p&gt;
&lt;p&gt;I grabbed a spare aux cord from one of my box o&amp;#39; cables and connected it to my temperamental Bluetooth speaker, pressed play, and there was Pharrell letting me know he that he didn&amp;#39;t want to sound full of himself or rude.&lt;/p&gt;
&lt;p&gt;The buttons still work, too, though the screen&amp;#39;s backlight seems to have given up, and the volume dial insists on lowering no matter which direction I pushed.&lt;/p&gt;
&lt;p&gt;But it worked.&lt;/p&gt;
&lt;p&gt;It didn&amp;#39;t tell me that the device was disabled because RCA retired the product.&lt;/p&gt;
&lt;p&gt;It didn&amp;#39;t &lt;a href=&quot;https://twitter.com/search?q=aws%20down%20doorbell%20since%3A2020-11-25%20until%3A2020-11-27&quot;&gt;lock up&lt;/a&gt; because AWS US-East-1 was &lt;a href=&quot;https://twitter.com/search?q=aws%20down%20vacuum%20until%3A2020-11-26%20since%3A2020-11-25&quot;&gt;down&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It woke up and played the songs on the card.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
	
		
			
			<entry>
				<title>Media attributes and you(r network requests)</title>
				<link href="https://www.tattooed.dev/wrote/media-attributes-and-your-network-requests/"/>
				<updated>2020-04-07T23:00:00Z</updated>
				<id>urn:uuid:65f48853-a389-4d43-8771-1f2bf09c4570</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;While surveying my Twitter timeline this morning, wondering what fresh horrors awaited me, &lt;a href=&quot;https://webcloud.se/blog/2020-04-06-flash-of-unstyled-dark-theme/&quot;&gt;this blog post about FOUT and dark themes in Gatsby/Next sites&lt;/a&gt;. Neither Gatsby nor Next are parts of any of my stacks at the moment, but Daniel&amp;#39;s reasoning is pretty solid.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve been thinking about dark themes lately&amp;mdash;still need to make one for this here site&amp;mdash;and Daniel&amp;#39;s post sent me down that rabbit hole again.&lt;/p&gt;
&lt;p&gt;One of my personal hurdles is keeping my CSS as minimal as possible &lt;sup&gt;&lt;a href=&quot;#note1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, and including unnecessary dark theme-specific CSS (or light-themed, depending on your perspective; we do not theme-shame here) always struck me as bloat. Sure, bloat that makes for a better user experience, but bloat nevertheless.&lt;/p&gt;
&lt;p&gt;My first thought to solve this was putting splitting the theme-specific CSS into a separate file, and slapping a &lt;code&gt;media&lt;/code&gt; attribute on it.&lt;/p&gt;
&lt;p&gt;EXAMPLE CODE, GO!&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
&amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;
&amp;lt;title&amp;gt;Tinkering with media attributes, y&amp;#39;all&amp;lt;/title&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;default.css&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;dark.css&amp;quot; media=&amp;quot;(prefers-color-scheme: dark)&amp;quot; /&amp;gt;
&amp;lt;p&amp;gt;hi there&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		&lt;figcaption&gt;default.css&lt;/figcaption&gt;
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
	--body-bg-color: white;
	--body-text-color: black;
}

body {
	background: var(--body-bg-color);
	color: var(--body-text-color);
}&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		&lt;figcaption&gt;dark.css&lt;/figcaption&gt;
		&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
	--body-bg-color: black;
	--body-text-color: white;
}&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;If your OS is set to use light themes, you&amp;#39;ll see black text on a white background, and if your OS is set to use dark themes, you&amp;#39;ll see white text on a black background.&lt;/p&gt;
&lt;p&gt;Sometimes I forget that the &lt;code&gt;media&lt;/code&gt; attribute accepts any &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media&quot;&gt;valid media query&lt;/a&gt;, and it operates like &lt;code&gt;@media&lt;/code&gt; block rules in your CSS. But that&amp;#39;s what it does. It&amp;#39;s pretty neat.&lt;/p&gt;
&lt;p&gt;Now, the assumption I made here is that placing &lt;code&gt;dark.css&lt;/code&gt; in a separate file could be a performance gain because, for example, the browser would see &lt;code&gt;(prefers-color-scheme: dark)&lt;/code&gt; and not request the file if the visitor&amp;#39;s OS were set to use light themes.&lt;/p&gt;
&lt;p&gt;That assumption was and is very, very wrong.&lt;/p&gt;
&lt;p&gt;I opened the network tab in dev tools and saw that in Firefox, Chrome, Edge, and even IE, &lt;code&gt;dark.css&lt;/code&gt; was requested regardless of the OS settings. Just to make sure I wasn&amp;#39;t missing some typo, I changed &lt;code&gt;media&lt;/code&gt; to &lt;code&gt;print&lt;/code&gt; and got the same results, even though the styles weren&amp;#39;t applied to the document.&lt;/p&gt;
&lt;p&gt;I could&amp;#39;ve just accepted that as The Way and moved on, but reader, you are here for a blog post and that would not be very fun. So, LET&amp;#39;S GO TO THE SPEC! Specifically, HTML 5.2, section 4.2, with my own add emphasis.&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;If the link is a hyperlink then the &lt;code&gt;media&lt;/code&gt; attribute is purely advisory, and describes for which media the document in question was designed.&lt;/p&gt;
&lt;p&gt;However, if the link is an external resource link, then the &lt;code&gt;media&lt;/code&gt; attribute is prescriptive. &lt;em&gt;The user agent must apply the external resource when the &lt;code&gt;media&lt;/code&gt; attribute&amp;#39;s value matches the environment and the other relevant conditions apply, and must not apply it otherwise.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The default, if the &lt;code&gt;media&lt;/code&gt; attribute is omitted, is &amp;quot;&lt;code&gt;all&lt;/code&gt;&amp;quot;, meaning that by default links apply to all media.&lt;/p&gt;
&lt;p&gt;Note: The external resource might have further restrictions defined within that limit its applicability. For example, a CSS style sheet might have some &lt;code&gt;@media&lt;/code&gt; blocks. This specification does not override such further restrictions or requirements.&lt;/p&gt;

		&lt;/blockquote&gt;
		
			&lt;figcaption class=&quot;quote__caption&quot;&gt;
				
				
					&lt;cite class=&quot;quote__cite&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/html52/document-metadata.html#processing-link-media&quot;&gt;HTML 5.2: 4.2.4.1 Processing the media attribute&lt;/a&gt;&lt;/p&gt;
&lt;/cite&gt;
				
			&lt;/figcaption&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;That second paragraph contains the answer. The browser &lt;em&gt;must&lt;/em&gt; apply the styles in that resource when the media query matches the environment, and the thing we have to remember is that the environment can change at any time, for any number of reasons.&lt;/p&gt;
&lt;p&gt;In my example code, &lt;code&gt;dark.css&lt;/code&gt; is only applied if &lt;code&gt;(prefers-color-scheme: dark)&lt;/code&gt; evaluates to &lt;code&gt;true&lt;/code&gt;, but you can change your OS settings at any time, and the document styles should update accordingly (and in my opinion, with little to no effort on the part of you, the visitor).&lt;/p&gt;
&lt;p&gt;In the case of, say, setting the value of &lt;code&gt;media&lt;/code&gt; to &lt;code&gt;print&lt;/code&gt;, the browser doesn&amp;#39;t know when (or if) you&amp;#39;re going to print that page, so it needs to download it just in case.&lt;sup&gt;&lt;a href=&quot;#note2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;If we set &lt;code&gt;media&lt;/code&gt; to &lt;code&gt;(min-width: 500px)&lt;/code&gt;, the viewport may or may not be 500px wide when the page is loaded, or it may scale up or down after the fact, so the browser needs to be able to apply the styles when appropriate.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ll keep this list of scenarios at three for brevity (there&amp;#39;s a lot of possible media query situations) and because three is an aesthetically please number for me. We all win.&lt;/p&gt;
&lt;p&gt;The point is that the browser downloads the resource even if &lt;code&gt;media&lt;/code&gt; doesn&amp;#39;t match the environment because it &lt;em&gt;could&lt;/em&gt; match at some point. In other words, using &lt;code&gt;media&lt;/code&gt; is not a performance win. Womp womp.&lt;/p&gt;
&lt;h2 id=&quot;a-little-more-about-that-media-attribute&quot;&gt;A little more about that &lt;code&gt;media&lt;/code&gt; attribute&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s look back at the note included in section 4.2.4.1 of the spec: &amp;quot;The external resource might have further restrictions defined within that limit its applicability. For example, a CSS style sheet might have some &lt;code&gt;@media&lt;/code&gt; blocks. This specification does not override such further restrictions or requirements.&amp;quot;&lt;/p&gt;
&lt;p&gt;So the process goes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser downloads the resource regardless of whether &lt;code&gt;media&lt;/code&gt; matches&lt;/li&gt;
&lt;li&gt;Those styles are applied according to the outcome of &lt;code&gt;media&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;But any media queries found inside that resource are also evaluated and applied accordingly, regardless of the &lt;code&gt;media&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pretty straight forward if you ask me&amp;mdash;but no one did, so of course this isn&amp;#39;t how it works.&lt;/p&gt;
&lt;p&gt;The way step 3 actually happens is that the subsequent media queries in the linked resource are effectively nested within the value defined in &lt;code&gt;media&lt;/code&gt; (and remember, its default value is &lt;code&gt;all&lt;/code&gt; so if it isn&amp;#39;t explicitly defined, all of your linked media queries are then nested within an implied &lt;code&gt;@media all&lt;/code&gt; rule).&lt;/p&gt;
&lt;p&gt;For example, if you set &lt;code&gt;media&lt;/code&gt; to &lt;code&gt;(min-width: 500px)&lt;/code&gt; and included a &lt;code&gt;(min-width: 600px)&lt;/code&gt; media query, the styles defined in the latter would be applied because both conditions were met.&lt;/p&gt;
&lt;p&gt;However, if you set &lt;code&gt;media&lt;/code&gt; to &lt;code&gt;(width: 500px)&lt;/code&gt; and included a &lt;code&gt;(min-width: 600px)&lt;/code&gt; media query, the latter would never be applied because the viewport could not be both 500 pixels and 600 pixels wide simultaneously.&lt;/p&gt;
&lt;h2 id=&quot;ok-so-what-are-the-takeaways&quot;&gt;Ok, so what are the takeaways?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;media&lt;/code&gt; doesn&amp;#39;t prevent network requests&lt;/li&gt;
&lt;li&gt;Media queries in a linked stylesheet are nested within the condition in &lt;code&gt;media&lt;/code&gt; (with a default of &lt;code&gt;all&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;It&amp;#39;s ok to put your theme-specific CSS in with the rest of your CSS and work to keep it all streamlined&lt;/li&gt;
&lt;/ol&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Unsolicited MCU Power Rankings: Phases 1-3</title>
				<link href="https://www.tattooed.dev/wrote/unsolicited-mcu-power-rankings-phases-1-3/"/>
				<updated>2020-02-18T16:30:00Z</updated>
				<id>urn:uuid:cc3472e8-a33e-455e-8f40-dca6a5ae57b1</id>
				<content type="html">
					
					
						
							
								&lt;ol&gt;
&lt;li&gt;Captain America: The Winter Soldier&lt;/li&gt;
&lt;li&gt;Thor: Ragnarok&lt;/li&gt;
&lt;li&gt;Avengers: Endgame&lt;/li&gt;
&lt;li&gt;Black Panther&lt;/li&gt;
&lt;li&gt;Captain Marvel&lt;/li&gt;
&lt;li&gt;Spider-Man: Homecoming&lt;/li&gt;
&lt;li&gt;The Avengers&lt;/li&gt;
&lt;li&gt;Avengers: Infinity War&lt;/li&gt;
&lt;li&gt;Spider-Man: Far From Home&lt;/li&gt;
&lt;li&gt;Avengers: Age of Ultron&lt;/li&gt;
&lt;li&gt;Iron Man 2&lt;/li&gt;
&lt;li&gt;Ant-Man&lt;/li&gt;
&lt;li&gt;Guardians of the Galaxy&lt;/li&gt;
&lt;li&gt;Captain America: Civil War&lt;/li&gt;
&lt;li&gt;Guardians of the Galaxy Vol. 2&lt;/li&gt;
&lt;li&gt;Thor: The Dark World&lt;/li&gt;
&lt;li&gt;Ant-Man and the Wasp&lt;/li&gt;
&lt;li&gt;Iron Man&lt;/li&gt;
&lt;li&gt;Captain America: The First Avenger&lt;/li&gt;
&lt;li&gt;Doctor Strange&lt;/li&gt;
&lt;li&gt;Thor&lt;/li&gt;
&lt;li&gt;Iron Man 3&lt;/li&gt;
&lt;li&gt;The Incredible Hulk&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ok fine. I&amp;#39;ll defend a couple of things.&lt;/p&gt;
&lt;p&gt;First, it&amp;#39;s clear I am not a huge fan of origin stories. I&amp;#39;ve already accepted that the characters are not bound by the rules of reality, I don&amp;#39;t require a detailed origin story for every. single. character. This is also why &lt;i&gt;Black Panther&lt;/i&gt;, &lt;i&gt;Captain Marvel&lt;/i&gt;, and &lt;i&gt;Spider-Man: Homecoming&lt;/i&gt; are ranked so highly&amp;mdash; they spent more time building characters than they did showing the step-by-step process of how each hero became &amp;quot;enhanced.&amp;quot;&lt;/p&gt;
&lt;p&gt;Second, &lt;i&gt;Thor: The Dark World&lt;/i&gt; is in the middle-third, not the bottom-third, for one very real reason: this movie let Loki finish moving from fallen hero to failed villain to genuine trickster.&lt;/p&gt;
&lt;p&gt;And because I love thinking about things in terms of D&amp;amp;D alignments, Loki transitioned from Lawful Good to Chaotic Evil to Chaotic Neutral. THIS IS DEPTH, PEOPLE.&lt;/p&gt;
&lt;p&gt;Third, &amp;quot;omg how can you rank [MOVIE] so low?!&amp;quot; Because that&amp;#39;s how lists work.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>A Year&#39;s Worth of Movies</title>
				<link href="https://www.tattooed.dev/wrote/a-years-worth-of-movies/"/>
				<updated>2020-01-01T21:30:00Z</updated>
				<id>urn:uuid:f36ce2b3-0d9a-4d12-9b8e-e3de13f9bbc4</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;At the end of 2018, I realized I&amp;#39;d watched 21 movies over the course of my two-week holiday staycation. My love of movies is a well-known thing but this count genuinely surprised me, and like any reasonable person, I decided to keep track of how many movies I watched in the upcoming year.&lt;/p&gt;
&lt;p&gt;For science.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;12 Monkeys&lt;/li&gt;
&lt;li&gt;1922&lt;/li&gt;
&lt;li&gt;A Dark Song&lt;/li&gt;
&lt;li&gt;A Quiet Place&lt;/li&gt;
&lt;li&gt;Ad Astra&lt;/li&gt;
&lt;li&gt;Alita: Battle Angel&lt;/li&gt;
&lt;li&gt;The Apostle&lt;/li&gt;
&lt;li&gt;The Autopsy of Jane Doe&lt;/li&gt;
&lt;li&gt;Avengers: Endgame&lt;/li&gt;
&lt;li&gt;Await Further Instructions&lt;/li&gt;
&lt;li&gt;The Bad Batch&lt;/li&gt;
&lt;li&gt;The Black Godfather&lt;/li&gt;
&lt;li&gt;Booksmart&lt;/li&gt;
&lt;li&gt;Captain Marvel&lt;/li&gt;
&lt;li&gt;The Crimes of Grindelwald&lt;/li&gt;
&lt;li&gt;Doctor Sleep&lt;/li&gt;
&lt;li&gt;Doubt&lt;/li&gt;
&lt;li&gt;Drive&lt;/li&gt;
&lt;li&gt;Eli&lt;/li&gt;
&lt;li&gt;The Favourite&lt;/li&gt;
&lt;li&gt;The Gate&lt;/li&gt;
&lt;li&gt;Godzilla: City on the Edge of Battle&lt;/li&gt;
&lt;li&gt;Godzilla: Planet Eater&lt;/li&gt;
&lt;li&gt;Good Boys&lt;/li&gt;
&lt;li&gt;Gosford Park&lt;/li&gt;
&lt;li&gt;Grass is Greener&lt;/li&gt;
&lt;li&gt;Hannibal Burress: Comedy Camisado&lt;/li&gt;
&lt;li&gt;I Trapped the Devil&lt;/li&gt;
&lt;li&gt;The Informant!&lt;/li&gt;
&lt;li&gt;The Interview&lt;/li&gt;
&lt;li&gt;Into the Dark: Down&lt;/li&gt;
&lt;li&gt;Into the Dark: Pure&lt;/li&gt;
&lt;li&gt;IT: Chapter 2&lt;/li&gt;
&lt;li&gt;John Wick&lt;sup&gt;[&lt;a href=&quot;#note1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;John Wick: Chapter 2&lt;sup&gt;[&lt;a href=&quot;#note1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;John Wick: Chapter 3 – Parabellum&lt;/li&gt;
&lt;li&gt;Joker&lt;/li&gt;
&lt;li&gt;The Killing of a Sacred Deer&lt;/li&gt;
&lt;li&gt;Knives Out&lt;/li&gt;
&lt;li&gt;The Laundromat&lt;/li&gt;
&lt;li&gt;Locke&lt;/li&gt;
&lt;li&gt;Malevolent&lt;/li&gt;
&lt;li&gt;Michelle Wolf: Joke Show&lt;/li&gt;
&lt;li&gt;Midsommar&lt;/li&gt;
&lt;li&gt;The Monster&lt;/li&gt;
&lt;li&gt;The Muppet Christmas Carol&lt;/li&gt;
&lt;li&gt;Only God Forgives&lt;/li&gt;
&lt;li&gt;The Pact&lt;/li&gt;
&lt;li&gt;The Pelican Brief&lt;/li&gt;
&lt;li&gt;The Perfect Bid&lt;/li&gt;
&lt;li&gt;Pet Sematary (1989)&lt;/li&gt;
&lt;li&gt;Pet Sematary (2019)&lt;/li&gt;
&lt;li&gt;The Ritual&lt;/li&gt;
&lt;li&gt;The Running Man&lt;/li&gt;
&lt;li&gt;Scary Stories to Tell in the Dark&lt;/li&gt;
&lt;li&gt;Spider-Man: Far From Home&lt;/li&gt;
&lt;li&gt;Star Wars: The Rise of Skywalker&lt;/li&gt;
&lt;li&gt;Superbad&lt;/li&gt;
&lt;li&gt;The Terminator&lt;/li&gt;
&lt;li&gt;They Look Like People&lt;/li&gt;
&lt;li&gt;To Wong Foo, Thanks for Everything, Julie Newmar&lt;/li&gt;
&lt;li&gt;The Two Popes&lt;/li&gt;
&lt;li&gt;Us&lt;/li&gt;
&lt;li&gt;Velvet Buzzsaw&lt;/li&gt;
&lt;li&gt;The Void&lt;/li&gt;
&lt;li&gt;We Have Always Lived in the Castle&lt;/li&gt;
&lt;li&gt;Wig&lt;/li&gt;
&lt;li&gt;The Woman in Black&lt;/li&gt;
&lt;li&gt;XX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...Whew.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s 69 movies. Total run-time for all of them is 7464 minutes. 124.4 hours. 5.18 days.&lt;/p&gt;
&lt;p&gt;Near the end of December, I really thought I could push through and make it to 100 for the year, but alas, I had some level of sense left in my head. Still, 69 movies for the year is a pretty respectable achievement. Some might even say it&amp;#39;s... nice.&lt;/p&gt;
&lt;p&gt;Some of the movies were great, some were suspicious. I wish I had the audacity to write a review of each one, but rather I in fact do have the mercy not to do that to you. Instead, I&amp;#39;ve chosen my favorites that I think you&amp;mdash;yes, you&amp;mdash;should definitely watch.&lt;/p&gt;
&lt;h2 id=&quot;ithe-autopsy-of-jane-doei&quot;&gt;&lt;i&gt;The Autopsy of Jane Doe&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;I heard about &lt;i&gt;The Autopsy of Jane Doe&lt;/i&gt; by way of &lt;a href=&quot;https://twitter.com/slimyswampghost&quot;&gt;@slimyswampghost&lt;/a&gt; on Twitter: &amp;quot;A classically spooky mystery / horror story told through a single location, with some icky body horror. I guarantee you that after watching, the next time you hear a bell, you&amp;#39;ll shudder.&amp;quot;&lt;sup&gt;[&lt;a href=&quot;#note2&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;

							
						
							
								
	

	
		
		
			&lt;picture&gt;
				
				&lt;img
					class=&quot;blog-image--block-center&quot;
					src=&quot;https://assets.tattooed.dev/images/movie-autopsy-jane-doe.jpg&quot;
					width=&quot;1280&quot;
					height=&quot;720&quot;
					loading=&quot;lazy&quot;
					alt=&quot;A woman&amp;#39;s body on a medical examiner&amp;#39;s table.&quot; /&gt;
			&lt;/picture&gt;
		
	
	

							
						
							
								&lt;p&gt;If it hadn&amp;#39;t been called out to me ahead of time, I never would&amp;#39;ve noticed that the entire 86-minute movie takes place in a single, solitary location. That&amp;#39;s how lost I got in the &amp;quot;WHAT IS HAPPENING&amp;quot; moments. I didn&amp;#39;t even try to figure out who Jane Doe was, and I didn&amp;#39;t care, because WHAT.&lt;/p&gt;
&lt;p&gt;Any movie that can make me stop trying to figure things out myself is a winner.&lt;/p&gt;
&lt;p&gt;This movie also brought a happy accident with it, for me: a few weeks later, I saw that the director, André Øvredal, was also directing &lt;i&gt;Scary Stories to Tell in the Dark&lt;/i&gt;, which then blew my expectations for it through the roof. (Spoiler: I wasn&amp;#39;t disappointed.)&lt;/p&gt;
&lt;h2 id=&quot;idoctor-sleepi&quot;&gt;&lt;i&gt;Doctor Sleep&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;I was really disappointed in &lt;i&gt;IT: Chapter 2&lt;/i&gt; for a lot of the same reasons other people claimed: too long, not scary, blah blah blah. So my expectations for &lt;i&gt;Doctor Sleep&lt;/i&gt; were really low. So low that I didn&amp;#39;t really even know much about it beforehand.&lt;/p&gt;
&lt;p&gt;Truthfully, I skew toward hyperbole more than I&amp;#39;d like to admit, but this is exactly how it went down: when the movie ended, I stayed in my theater seat, next to my friend, dumbfounded by how good it was. We turned toward one another and said &amp;quot;WOW&amp;quot; in unison.&lt;/p&gt;
&lt;p&gt;The movie&amp;#39;s runtime of 2.5 hours had me nervous, but there wasn&amp;#39;t a single wasted moment. The effects were smooth and believable, and more than anything else, intentionally jarring. I think it was successfully scary because the story&amp;#39;s progression depended on &amp;quot;but--but HOW?&amp;quot; anxiety, if that makes sense. And instead of building that suspense and doing nothing with it, I thought things were resolved just right.&lt;/p&gt;
&lt;h2 id=&quot;igood-boysi&quot;&gt;&lt;i&gt;Good Boys&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;My gold standard for laughing at a movie is &lt;i&gt;Mean Girls&lt;/i&gt;, when I was nearly asked to leave the theater because I couldn&amp;#39;t stop laughing after Regina George was hit by the school bus.&lt;/p&gt;
&lt;p&gt;Friends, this movie didn&amp;#39;t just surpass that standard. It took the standard, crumpled it into a ball, tossed it into the trash, and set the trash on fire.&lt;sup&gt;[&lt;a href=&quot;#note3&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;ijohn-wick-chapter-3--parabellumi&quot;&gt;&lt;i&gt;John Wick: Chapter 3 – Parabellum&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;If you thought I wouldn&amp;#39;t include a John Wick movie in my favorites, &lt;a href=&quot;https://tattooed.dev/wrote/john-wick-constantine/&quot;&gt;this must be your first time meeting me&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Chapter 3&lt;/i&gt; is here because it&amp;#39;s just plain good, but also because I&amp;#39;m in love with the franchise&amp;#39;s world-building. Nearly every character has a very real backstory, but the main story doesn&amp;#39;t take a detour in order to explain each one. We&amp;#39;re given enough information to know how much something matters, but we are still in the main story the entire time.&lt;/p&gt;
&lt;p&gt;I will go to the grave screaming this: JOHN WICK IS HOW YOU DO ORIGIN STORIES IN MOVIES. Period.&lt;/p&gt;
&lt;h2 id=&quot;ithe-rituali&quot;&gt;&lt;i&gt;The Ritual&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;A friend of mine watched &lt;i&gt;The Ritual&lt;/i&gt; on my recommendation, and came back to me with &amp;quot;I will never listen to your suggestions ever again.&amp;quot;&lt;/p&gt;
&lt;p&gt;We&amp;#39;re still friends, because I do not keep friends based on their bad opinions.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;The Ritual&lt;/i&gt; is fantastic.&lt;/p&gt;

							
						
							
								
	

	
		
		
			&lt;picture&gt;
				
				&lt;img
					class=&quot;blog-image--block-center&quot;
					src=&quot;https://assets.tattooed.dev/images/movie-ritual.jpg&quot;
					width=&quot;1267&quot;
					height=&quot;634&quot;
					loading=&quot;lazy&quot;
					alt=&quot;An animal skull with bundles of sticks attached to appear like horns, hung on a body made of leather and wood, set against a dark, gray sky.&quot; /&gt;
			&lt;/picture&gt;
		
	
	

							
						
							
								&lt;p&gt;It uses but doesn&amp;#39;t depend on jump scares. And I appreciate that it doesn&amp;#39;t reveal the monster early. You spend most of the movie wondering what the hell is happening to the main characters, but you get mixed clues. Is it a monster? Is it a witch? Is it Jason Voorhees but German?&lt;/p&gt;
&lt;p&gt;What you get is one of the most well designed monsters I&amp;#39;ve seen in a while.&lt;/p&gt;
&lt;h2 id=&quot;iusi&quot;&gt;&lt;i&gt;Us&lt;/i&gt;&lt;/h2&gt;

							
						
							
								
	

	
		
		
			&lt;picture&gt;
				
				&lt;img
					class=&quot;blog-image--block-center&quot;
					src=&quot;https://assets.tattooed.dev/images/movie-us.jpg&quot;
					width=&quot;1200&quot;
					height=&quot;800&quot;
					loading=&quot;lazy&quot;
					alt=&quot;A close-up picture of a black woman holding her head in her hands, with a tan driving glove on her right hand, mouth slightly agape, staring into the camera.&quot; /&gt;
			&lt;/picture&gt;
		
	
	

							
						
							
								&lt;p&gt;After &lt;i&gt;Get Out&lt;/i&gt;, Jordan Peele has to work to make me not love his work. His approach to storytelling just clicks with me.&lt;/p&gt;
&lt;p&gt;And now, whenever I hear &amp;quot;I Got 5 on It,&amp;quot; all I can think about is that climactic fight scene. For him to just roll up in 2019 with a movie that replaces any and every memory associated with a song I&amp;#39;ve loved since I was a kid... how dare he.&lt;/p&gt;
&lt;p&gt;I was impressed as hell (but not surprised) by how well Lupita Nyong&amp;#39;o drew distinctions between Adelaide and Red. Red&amp;#39;s ticks and mannerisms were just so well developed.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Us&lt;/i&gt; is also one of those movies where I can&amp;#39;t choose my favorite character because every character is my favorite character.&lt;/p&gt;
&lt;h2 id=&quot;honorable-mentions&quot;&gt;Honorable Mentions&lt;/h2&gt;
&lt;p&gt;With one exception&lt;sup&gt;[&lt;a href=&quot;#note4&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;, I think any and all of the movies on this list are worth watching. I watched some this year that were so bad, I refused to finish them, so they didn&amp;#39;t make the list.&lt;/p&gt;
&lt;p&gt;The label &amp;quot;honorable mentions&amp;quot; is such an understatement. These movies are really, really good, I just had to put some limits on myself here: &lt;i&gt;A Dark Song&lt;/i&gt;, &lt;i&gt;Grass is Greener&lt;/i&gt;, &lt;i&gt;Knives Out&lt;/i&gt;, &lt;i&gt;Midsommar&lt;/i&gt;, &lt;i&gt;They Look Like People&lt;/i&gt;, &lt;i&gt;The Void&lt;/i&gt;.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>The most wonderful time of the year: Advent of Code</title>
				<link href="https://www.tattooed.dev/wrote/the-most-wonderful-time-of-the-year-advent-of-code/"/>
				<updated>2019-12-01T06:00:00Z</updated>
				<id>urn:uuid:f2835a29-0565-4ab0-af48-b0c9b5915c16</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;For the third year, I&amp;#39;m participating in this year&amp;#39;s &lt;a href=&quot;https://adventofcode.com&quot;&gt;Advent of Code&lt;/a&gt;:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;quote&quot;&gt;
		&lt;blockquote class=&quot;quote__text&quot;&gt;
			&lt;p&gt;Advent of Code is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like. People use them as a speed contest, interview prep, company training, university coursework, practice problems, or to challenge each other.&lt;/p&gt;

		&lt;/blockquote&gt;
		
			&lt;figcaption class=&quot;quote__caption&quot;&gt;
				
				
					&lt;cite class=&quot;quote__cite&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://adventofcode.com/2019/about&quot;&gt;Advent of Code&lt;/a&gt;&lt;/p&gt;
&lt;/cite&gt;
				
			&lt;/figcaption&gt;
		
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;I&amp;#39;m posting &lt;a href=&quot;https://github.com/vfalconi/advent-of-code-2019&quot;&gt;my answers on GitHub&lt;/a&gt;, but I may try my hand at live-ish blogging about with updates on this post.&lt;/p&gt;
&lt;p&gt;My goals this year are simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forget the leaderboard.&lt;/strong&gt; Each puzzle is released at midnight Eastern, so 11pm my time. AoC is for fun, but not the kind of fun that moves me to sacrifice sleep.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complete day 8.&lt;/strong&gt; I&amp;#39;ve either gotten stuck by the puzzle or overwhelmed with life/work by day 7. The latter doesn&amp;#39;t seem like a factor this year (fingers crossed), and I hope forgetting the leaderboard helps with the former. :)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experiment.&lt;/strong&gt; Last year, I started using new data structures I don&amp;#39;t use on a daily basis, and I found out how helpful sets and maps can be. On top of that, I am the world&amp;#39;s worst when it comes to testing code, so I&amp;#39;m using &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; to get better about it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;day-1&quot;&gt;Day 1&lt;/h2&gt;
&lt;p&gt;Personally, day 1&amp;#39;s puzzles weren&amp;#39;t too difficult, but that was definitely welcome, since I needed to setup Jest and write tests for the first time ever &lt;sup&gt;&lt;a href=&quot;#note1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I found Jest&amp;#39;s API really easy to pick up, and I&amp;#39;m sure I&amp;#39;ll find more &amp;quot;Oh, so that&amp;#39;s what a test should do?&amp;quot; moments with it as I go along.&lt;/p&gt;
&lt;h2 id=&quot;day-2&quot;&gt;Day 2&lt;/h2&gt;
&lt;p&gt;I really enjoyed this day&amp;#39;s puzzle. I had two head-scratchers though.&lt;/p&gt;
&lt;p&gt;First, the wording of part 2 tripped me up pretty badly. It took me a long time to understand what it was asking me to do, and I kept looking at the numbers, thinking that there was a typo or maybe I&amp;#39;d received the wrong puzzle input somehow. Nope, just looking at the problem the wrong way.&lt;/p&gt;
&lt;p&gt;Second, while trying to sort out the first head-scratcher, I thought I was in the Upside Down because some variables were changing without any clear reason. Specifically, when I thought I made a copy of an array, suddenly the original array was changing in sync with the copy.&lt;/p&gt;
&lt;p&gt;It took me way too long to realize it was because I wasn&amp;#39;t making deep copies: using a mere &lt;code&gt;=&lt;/code&gt; to make a copy of an array only points that variable at the array you&amp;#39;re attempting to copy. So, declaring &lt;code&gt;const copy = array&lt;/code&gt;, then setting &lt;code&gt;copy[1] = &amp;#39;pants&amp;#39;&lt;/code&gt;, &lt;code&gt;array[1]&lt;/code&gt; will also return &lt;code&gt;&amp;#39;pants&amp;#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to copy an array (or an object), use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax&quot;&gt;spread syntax&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;day-3&quot;&gt;Day 3&lt;/h2&gt;
&lt;p&gt;Ha. Haha. Oh boy. Remember when I started this, and I tempted the fates by saying that life didn&amp;#39;t look like it was going to become overwhelming? I was very, very wrong.&lt;/p&gt;
&lt;p&gt;I solved day 3 while traveling earlier this month, and didn&amp;#39;t take any notes. From the comments I left myself in my code, I had no problem with part one, but it seems my answers in part two were off by two digits. I decided this was an off-by-two error (???), and normally I&amp;#39;d go back and figure out what went wrong, but I got the correct answer because my first guess was too low, and my second guess was too high, and the difference between the guesses was 2, so, uh, I&amp;#39;m not going to rework it. Because I don&amp;#39;t have to.&lt;/p&gt;
&lt;h2 id=&quot;day-4&quot;&gt;Day 4&lt;/h2&gt;
&lt;p&gt;I start solving day 4 while I was still on the road, and had to put it away once I got home because life &lt;del&gt;got in the way&lt;/del&gt; took priority. I picked it back up today, and got through part two after looking at how others approached it. I knew I should&amp;#39;ve been using regex and &lt;code&gt;indexOf&lt;/code&gt;/&lt;code&gt;lastIndexOf&lt;/code&gt; but I just couldn&amp;#39;t see how the logic would work best.&lt;/p&gt;
&lt;p&gt;At some point around day 3, I realized, for the first time since starting to play this game in 2017, that you &lt;em&gt;do not&lt;/em&gt; have to solve each day in sequential order. I assumed that if I wanted to do day 4, the site wouldn&amp;#39;t let me unless I completed days 1-3 first. Had I bothered to even try clicking other days in the calendar, I would&amp;#39;ve seen that the days were only time-locked.&lt;/p&gt;
&lt;p&gt;So now I am jumping around, avoiding the IntCode puzzles for as long as I can.&lt;/p&gt;
&lt;h2 id=&quot;day-8&quot;&gt;Day 8&lt;/h2&gt;
&lt;p&gt;This is probably my favorite puzzle out of all of the AoC puzzles I&amp;#39;ve ever attempted. When I saw that we were playing with pixels, I started working with the intention of producing an actual image in the end. Turned out, ASCII art was just fine:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.tattooed.dev/aoc-day-8-answer.png&quot; alt=&quot;ASCII block characters that spell out &amp;quot;CGEGE&amp;quot;&quot;&gt;&lt;/p&gt;

							
						
							
								
	

	
		
		
			&lt;picture&gt;
				
				&lt;img
					class=&quot;blog-image--block-center&quot;
					src=&quot;https://assets.tattooed.dev/images/aoc-day-8-answer.png&quot;
					width=&quot;258&quot;
					height=&quot;128&quot;
					loading=&quot;lazy&quot;
					alt=&quot;ASCII block characters that spell out &amp;quot;CGEGE&amp;quot;&quot; /&gt;
			&lt;/picture&gt;
		
	
	

							
						
							
								&lt;p&gt;More than anything else, I&amp;#39;m really liking Jest, and I think I&amp;#39;m improving with not just writing tests but &lt;em&gt;why&lt;/em&gt; I&amp;#39;m writing tests.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Eleventy and Craft</title>
				<link href="https://www.tattooed.dev/wrote/eleventy-and-craft/"/>
				<updated>2019-10-15T22:00:00Z</updated>
				<id>urn:uuid:fbf72078-28b8-4eb9-bf13-9f92805faf39</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;Earlier this summer, I switched this site from Jekyll to &lt;a href=&quot;https://www.11ty.io&quot;&gt;Eleventy&lt;/a&gt;. Jekyll was fine, but I&amp;#39;m not a Ruby developer, so when I ran into issues or wanted to extend functionality a little bit, I was stuck. Not that big of a deal, because I didn&amp;#39;t need that much extra functionality, but then I ran into build errors. Suddenly Ruby, or Jekyll, or Bundler, or &lt;em&gt;something&lt;/em&gt; was behind or ahead by a couple versions, and I couldn&amp;#39;t get anything to work anymore. Not only could I not add functionality, now I couldn&amp;#39;t manage my site at all.&lt;/p&gt;
&lt;p&gt;&amp;quot;BUT VINCE,&amp;quot; you might be saying, &amp;quot;THIS IS A GREAT OPPORTUNITY TO &lt;strong&gt;&lt;em&gt;LEARN RUBY&lt;/em&gt;&lt;/strong&gt;. EMBRACE IT.&amp;quot;&lt;/p&gt;
&lt;p&gt;Learn Ruby? In THIS economy?!&lt;/p&gt;
&lt;p&gt;Instead, I looked into this Eleventy &lt;abbr&gt;SSG&lt;/abbr&gt; (static site generator) I&amp;#39;d heard everyone talking about. A few hours later, I was up and running. Surely, this would help encourage me to write more, like I&amp;#39;d promised myself so many times before.&lt;/p&gt;
&lt;p&gt;A few months pass, and nothing happens.&lt;/p&gt;
&lt;p&gt;It took me that long to pinpoint my issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I wanted deployment to be automagical, but I didn&amp;#39;t want to deal with deploying from a repo. Hell, the thought alone of dealing with webhooks for this little ole site gives me hives.&lt;/li&gt;
&lt;li&gt;I enjoy using Markdown, but writing blog posts in a dev environment, then pushing them up to a repo, then logging into my server and doing a build there was such a hassle, I avoided publishing anything I&amp;#39;d managed to finish writing.&lt;/li&gt;
&lt;li&gt;I wanted to keep my site repo open, but writing blog posts over time in an open repo makes me really anxious.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything I considered kept telling me I need to separate code from content&amp;mdash;a radical idea, I know. So now, this site is generated by Eleventy using &lt;a href=&quot;https://craftcms.com/&quot;&gt;Craft CMS&lt;/a&gt;, and it&amp;#39;s (mostly) &lt;a href=&quot;https://github.com/vfalconi/tattooed.dev&quot;&gt;found on GitHub&lt;/a&gt;. (And this is the first blog post I&amp;#39;ve published using this method. NEAT.)&lt;/p&gt;
&lt;p&gt;I did this by separating the build process into three pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The JSON API&lt;/li&gt;
&lt;li&gt;Eleventy&amp;#39;s configuration and templates&lt;/li&gt;
&lt;li&gt;A &amp;quot;smarter&amp;quot; build process&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;the-json-api&quot;&gt;The JSON API&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/craftcms/element-api&quot;&gt;Craft&amp;#39;s Element API plugin&lt;/a&gt; makes it easy to define some endpoints, tell it what content to include, and even do a little finessing of things before it is sent back to my Eleventy build script.&lt;/p&gt;
&lt;p&gt;Since every site is special and unique, I won&amp;#39;t get into my endpoints&amp;#39; specifics, but assuming &lt;a href=&quot;https://docs.craftcms.com/v3/installation.html&quot;&gt;you installed Craft&lt;/a&gt; and &lt;a href=&quot;https://github.com/craftcms/element-api#installation&quot;&gt;the Elements API plugin&lt;/a&gt;, and created a channel-type section named &amp;quot;Blog&amp;quot; with some fields named &lt;code&gt;summary&lt;/code&gt; and &lt;code&gt;blogPost&lt;/code&gt;, your &lt;code&gt;elements-api.php&lt;/code&gt; config file would look something like this:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;use craft\elements\Entry;
use craft\helpers\UrlHelper;

return [
  &amp;#39;endpoints&amp;#39; =&amp;gt; [
    &amp;#39;entries.json&amp;#39; =&amp;gt; function () {
      return [
        &amp;#39;elementType&amp;#39; =&amp;gt; Entry::class,
        &amp;#39;resourceKey&amp;#39; =&amp;gt; &amp;#39;entries&amp;#39;,
        &amp;#39;paginate&amp;#39; =&amp;gt; false,
        &amp;#39;criteria&amp;#39; =&amp;gt; [
          &amp;#39;section&amp;#39; =&amp;gt; &amp;#39;blog&amp;#39;,
          &amp;#39;orderBy&amp;#39; =&amp;gt; &amp;#39;postDate asc&amp;#39;
        ],
        &amp;#39;transformer&amp;#39; =&amp;gt; function(Entry $entry) {
          return [
            &amp;#39;layout&amp;#39; =&amp;gt; &amp;#39;blog-post&amp;#39;,
            &amp;#39;title&amp;#39; =&amp;gt; $entry-&amp;gt;title,
            &amp;#39;id&amp;#39; =&amp;gt; $entry-&amp;gt;id,
            &amp;#39;date&amp;#39; =&amp;gt; $entry-&amp;gt;postDate-&amp;gt;format(&amp;#39;Y-m-d&amp;#39;),
            &amp;#39;published_at&amp;#39; =&amp;gt; $entry-&amp;gt;postDate-&amp;gt;format(\DateTime::ATOM),
            &amp;#39;permalink&amp;#39; =&amp;gt; $entry-&amp;gt;uri.&amp;#39;/&amp;#39;,
            &amp;#39;url&amp;#39; =&amp;gt; $entry-&amp;gt;uri.&amp;#39;/&amp;#39;,
            &amp;#39;description&amp;#39; =&amp;gt; $entry-&amp;gt;summary,
            &amp;#39;post&amp;#39; =&amp;gt; $entry-&amp;gt;blogPost,
          ];
        }
      ];
    }
  ]
];&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;If you were to visit &lt;code&gt;example.com/entries.json&lt;/code&gt;, you&amp;#39;d receive something like this:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-apacheconf&quot;&gt;{
  &amp;quot;entries&amp;quot;: [
    {
      &amp;quot;layout&amp;quot;: &amp;quot;blog-post&amp;quot;,
      &amp;quot;title&amp;quot;: &amp;quot;An exciting blog post&amp;quot;,
      &amp;quot;id&amp;quot;: &amp;quot;10019&amp;quot;,
      &amp;quot;date&amp;quot;: &amp;quot;2019-09-09&amp;quot;,
      &amp;quot;published_at&amp;quot;: &amp;quot;2019-09-09T10:58:00-07:00&amp;quot;,
      &amp;quot;permalink&amp;quot;: &amp;quot;blog/an-exciting-blog-post&amp;quot;,
      &amp;quot;url&amp;quot;: &amp;quot;blog/an-exciting-blog-post&amp;quot;,
      &amp;quot;description&amp;quot;: &amp;quot;This is a blog post on my new site.&amp;quot;,
      &amp;quot;post&amp;quot;: &amp;quot;Infographic infrastructure business plan stock metrics iteration iPad client equity influencer value proposition startup traction termsheet. Holy grail paradigm shift bootstrapping interaction design. Product management non-disclosure agreement gen-z startup innovator stock focus leverage. Stealth buzz iPad seed round virality ramen.\n\nPivot product management ownership investor marketing launch party mass market incubator early adopters analytics buzz first mover advantage infrastructure interaction design. Creative focus innovator partner network client pitch business plan. Angel investor innovator network effects return on investment infographic. Early adopters A/B testing analytics equity agile development buyer research &amp;amp; development supply chain partner network growth hacking.\n\nIncubator seed round A/B testing mass market market bootstrapping partnership influencer branding vesting period series A financing social media backing. Assets lean startup leverage mass market direct mailing network effects. Beta responsive web design partnership paradigm shift ramen virality accelerator business-to-consumer ecosystem mass market launch party. Bandwidth incubator first mover advantage business-to-consumer marketing long tail pitch product management freemium channels.&amp;quot;
    },
    ...
  ]
}&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;In addition to the Elements API plugin, &lt;a href=&quot;https://craftcms.com/blog/craft-33&quot;&gt;Craft released version 3.3&lt;/a&gt;, with support for a headless mode and built-in GraphQL API, no plugin needed. I was nearly finished with this process when they released v3.3, and since any GraphQL API was a bit overpowered for my purposes, I decided not to backtrack and redo things using these new built-in features.&lt;/p&gt;
&lt;p&gt;You do you, though. I believe in you and all of your wildest dreams.&lt;/p&gt;
&lt;h2 id=&quot;eleventys-configuration-and-templates&quot;&gt;Eleventy&amp;#39;s configuration and templates&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re interested in Eleventy&amp;#39;s general configuration, I refer you to &lt;a href=&quot;https://www.11ty.io/docs/&quot;&gt;its wonderful documentation&lt;/a&gt;. There&amp;#39;s a lot in there, but it&amp;#39;s well organized, and pretty accessible to all JavaScript skill levels.&lt;/p&gt;
&lt;p&gt;I need Eleventy to consume and build pages from this JSON API, so I decided to use &lt;code&gt;fetch&lt;/code&gt; (because it&amp;#39;s the tool I&amp;#39;m most familiar with, but Node has several options for making HTTP requests; follow your heart on this one):&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;eleventyConfig.addCollection(&amp;#39;blogPosts&amp;#39;, async function(collection) {
    collection = await fetch(process.env.BLOG_ENDPOINT).then(resp =&amp;gt; {
      if (resp.ok) return resp.json();
      throw new Error(&amp;#39;network error&amp;#39;);
    }).then(resp =&amp;gt; {
      return resp.entries;
    }).catch(err =&amp;gt; console.log(err));
});&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;You may have noticed that there is a second, seemingly unnecessary &lt;code&gt;.then()&lt;/code&gt; method in the chain. I did that as a shortcut because, by default, the Elements API returns a JSON object with two root properties, &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;meta&lt;/code&gt;, the latter containing pagination data, which is of no use to me here.&lt;/p&gt;
&lt;p&gt;What to do now that Eleventy is retrieving the JSON? Make it usable. Building on the above code:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;eleventyConfig.addCollection(&amp;#39;blogPosts&amp;#39;, async function(collection) {
	collection = await fetch(process.env.BLOG_ENDPOINT).then(resp =&amp;gt; {
		if (resp.ok) return resp.json();
		throw new Error(&amp;#39;network error&amp;#39;);
	}).then(resp =&amp;gt; {
		return resp.entries;
	}).catch(err =&amp;gt; console.log(err));

	collection.map(post =&amp;gt; {
		post.published_at = new Date(post.published_at);
		post.parsed = md(post.post);

		return post;
	});

	return collection;
});&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;At this point, I iterate over every entry returned by the API, converting each entry&amp;#39;s &lt;code&gt;published_at&lt;/code&gt; value to a proper JavaScript &lt;code&gt;Date()&lt;/code&gt; object, and also converting the post&amp;#39;s Markdown here, rather than in a template.&lt;/p&gt;
&lt;p&gt;Hooray, now Eleventy has a &lt;code&gt;collection&lt;/code&gt; named &amp;quot;blogPosts&amp;quot; that it can work with as if it were a set of Markdown files in its &lt;code&gt;src&lt;/code&gt; folder. In my setup, I have a template named &lt;code&gt;blog-posts.html&lt;/code&gt; with the following front-matter:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
pagination:
  data: collections.blogPosts
  size: 1
  alias: post
  addAllPagesToCollections: true
permalink: &amp;#39;{{ post.url }}&amp;#39;
layout: base
---&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;Again, I&amp;#39;ll defer to &lt;a href=&quot;https://www.11ty.io/docs/pagination/&quot;&gt;Eleventy&amp;#39;s documentaion on pagination&lt;/a&gt; for specifics, but this front-matter is telling Eleventy to use the collection we added from the API (&lt;code&gt;collections.blogPosts&lt;/code&gt;) and treat it as if the &lt;code&gt;data&lt;/code&gt; were coming from files in &lt;code&gt;src&lt;/code&gt;, regardless of what templating engine you choose.&lt;/p&gt;
&lt;h2 id=&quot;a-smarter-build-process&quot;&gt;A &amp;quot;smarter&amp;quot; build process&lt;/h2&gt;
&lt;p&gt;Here is where I got hung up for a while. My goal was automating the Eleventy build, but I also didn&amp;#39;t want to run Eleventy if I didn&amp;#39;t need to. Out of the box, Eleventy doesn&amp;#39;t provide a way to conditionally build&amp;mdash;and really, why would it? I knew I could detect changes in content simply by hashing the API response, and running the build step if there had been a change, so I set out to write a cron job to handle this.&lt;/p&gt;
&lt;p&gt;Because that makes sense, right?&lt;/p&gt;
&lt;p&gt;Sure... and then it didn&amp;#39;t work. Things were executed, but the errors piled up, mostly the &amp;quot;THIS VARIABLE IS UNDEFINED&amp;quot; variety. So I wrote some &lt;em&gt;more&lt;/em&gt; code to account for how cron works with Node&lt;sup&gt;&lt;a href=&quot;#note-2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;But you know what&amp;#39;s fun about researching &amp;quot;Node&amp;quot; and &amp;quot;cron?&amp;quot; You don&amp;#39;t find a ton of information about running Node scripts via cron, instead you get a lot of information about &lt;a href=&quot;https://github.com/node-cron/node-cron&quot;&gt;node-cron&lt;/a&gt;, a Node package that allows you to schedule tasks in a Node app using the same syntax as cron (more or less). I tossed this information to the side because I didn&amp;#39;t want something like cron, I wanted to run a Node script via Cron, dammit. Give me THAT knowledge, plz, google.&lt;/p&gt;
&lt;p&gt;Dear reader, I should&amp;#39;ve taken the hint.&lt;/p&gt;
&lt;p&gt;So here&amp;#39;s my new and improved, node-cron-powered &lt;code&gt;scheduled-build.js&lt;/code&gt;:&lt;/p&gt;

							
						
							
								
	&lt;figure class=&quot;code-snippet&quot;&gt;
		
		&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;require(&amp;quot;dotenv&amp;quot;).config();
const cron = require(&amp;#39;node-cron&amp;#39;);
const shell = require(&amp;#39;shelljs&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);
const { DateTime } = require(&amp;quot;luxon&amp;quot;);
const fetch = require(&amp;#39;node-fetch&amp;#39;);
const hash = require(&amp;#39;object-hash&amp;#39;);
const buildLock = (fs.existsSync(&amp;#39;build.lock&amp;#39;) ? fs.readFileSync(&amp;#39;build.lock&amp;#39;, &amp;#39;utf8&amp;#39;) : null);
const buildTime = DateTime.local().toLocaleString(DateTime.DATETIME_MED);
let results = null;
let entriesHash = null;

cron.schedule(&amp;#39;*/15 * * * *&amp;#39;, async () =&amp;gt; {
	// fetch the entries (in JSON) from the CMS for comparison purposes
	// i send the CMS a header that contains the build environment, because i use it
	// to determine whether to include drafts/pending entries or leave them out
	// this is really helpful when i&amp;#39;m using certain markup for the first time and need
	// to work through the styling (ex: tables, figures, etc)
	const apiResponse = await fetch(process.env.BUILD_BLOG_ENDPOINT, {
		&amp;#39;headers&amp;#39;: {
			&amp;#39;x-build-environment&amp;#39;: process.env.BUILD_ENVIRONMENT
		}
	}).then(resp =&amp;gt; {
		if (resp.ok) return resp.json();
		throw Error(&amp;#39;network error&amp;#39;);
	}).catch(err =&amp;gt; {
		return `${err.type.toUpperCase()} ERROR (#${err.errno}: ${err.code}): ${err.message}`;
	});

	if (typeof apiResponse === &amp;#39;object&amp;#39;) {
		// if fetch was successful, i&amp;#39;ll have a JSON object, so do the build stuff

		// this happens
		entriesHash = hash(apiResponse);

		if (entriesHash !== buildLock) {
			await shell.exec(`npm run-script build`);

			// update/write the build lock
			await fs.writeFileSync(&amp;#39;build.lock&amp;#39;, entriesHash);

			results = `build script ran`;
		} else {
			results = &amp;#39;no build, lock matched response&amp;#39;;
		}
	} else {
		// the fetch was not successful
		results = apiResponse;
	}

	// output the events of the quarter-hour
	console.group(`build for ${buildTime}`)
		console.log(`build lock: ${buildLock}`);
		console.log(`entries hash: ${entriesHash}`)
		console.group(&amp;#39;Settings&amp;#39;);
			if (process.getuid) console.log(`Current uid: ${process.getuid()}`);
			// manually adding/removing dotenv variables from this output was annoying as hell
			Object.keys(process.env).forEach(key =&amp;gt; {
				if (key.substr(0,6) === &amp;#39;BUILD_&amp;#39;) console.log(`${key}: ${process.env[key]}`);
			});
		console.groupEnd();
		console.group(&amp;#39;Result&amp;#39;);
			console.log(results);
		console.groupEnd();
	console.groupEnd();
	console.log(&amp;#39;========================================================================&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
	&lt;/figure&gt;

							
						
							
								&lt;p&gt;This &lt;em&gt;works&lt;/em&gt; and after scratching my head for a while, it&amp;#39;s a relief. But scheduling tasks via node-cron only works if the script itself is running.&lt;/p&gt;
&lt;p&gt;Hello, &lt;a href=&quot;https://github.com/Unitech/pm2&quot;&gt;PM2&lt;/a&gt;. PM2 keeps things running without my involvement, AND it solves the issue of logging build results. HOORAY.&lt;/p&gt;
&lt;h2 id=&quot;thats-it-thats-the-post&quot;&gt;That&amp;#39;s it. That&amp;#39;s the post.&lt;/h2&gt;
&lt;p&gt;I&amp;#39;m pretty happy with how things turned out, but there are a couple of gotchas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The scheduled build process runs both Eleventy &lt;em&gt;and&lt;/em&gt; Gulp, but only if Eleventy&amp;#39;s content has changed. If I make some CSS or JavaScript changes, I still need to run the build script locally and upload the new CSS files manually. It would be nice to have the script check for changes relevant only to Gulp, too.&lt;/li&gt;
&lt;li&gt;The process does not have any support for &lt;em&gt;deleting&lt;/em&gt; posts. If I take down a post, Eleventy sees that &lt;em&gt;something&lt;/em&gt; has changed, but there isn&amp;#39;t anything in place to tell it &amp;quot;hey, build the site, but also delete these things, too.&amp;quot; Not a deal-breaker, but also not critical to me right now.&lt;/li&gt;
&lt;li&gt;Closely related to (2), I noticed that Craft hid a couple of entries from the API response (they weren&amp;#39;t visible in the control panel, either!) that only returned after I ran an update on the CMS. Don&amp;#39;t know what happened, but if I fix gotcha #2 and this happens again, it means content disappears from the site without me knowing it. This is part of the reason I added as much logging as I did.&lt;/li&gt;
&lt;/ol&gt;

							
						
					
				</content>
			</entry>
		
	
		
			
			<entry>
				<title>Fathom: Less Creepy Analytics</title>
				<link href="https://www.tattooed.dev/wrote/fathom-less-creepy-analytics/"/>
				<updated>2019-05-12T18:00:00Z</updated>
				<id>urn:uuid:20c70489-6e1a-4cd5-b679-67ec4d39cde3</id>
				<content type="html">
					
					
						
							
								&lt;p&gt;Last May, I deactivated my Facebook account with no fanfare (friendfare?). A few weeks ago, I logged back in for the first time since and started the Delete Account process, and in a few days, my profile will be gone for good.&lt;sup&gt;[&lt;a href=&quot;#note1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;On top of &lt;a href=&quot;https://www.theguardian.com/commentisfree/2019/may/08/mark-zuckerberg-has-to-go&quot;&gt;the ever-growing list of reasons why Facebook is a house of horrors&lt;/a&gt;, I didn&amp;#39;t feel like I had a healthy relationship with the site. I spent too much time on it, and I rarely felt good after I used the site, I usually felt angry or hopeless. When I did feel good, it was because of the dopamine rush from Likes or comments. I spent less actual time with friends because I felt like I was already spending time with them all day on Facebook.&lt;/p&gt;
&lt;p&gt;At the bottom of it all, I was giving my time, and my information, to a shit-show of an advertising platform.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/gif/later-shitlords.gif&quot; alt=&quot;A clip of a child in a lamb onsie, pushing off and then reclining on a toy fourwheeler as it rolls down a hill, with the text &amp;#39;LATER SHITLORDS&amp;#39; superimposed.&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the months that followed, I began basically recommitting myself to protecting my privacy, and finding ways to do that for others.&lt;/p&gt;
&lt;p&gt;While implementing &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP&quot;&gt;Content Security Policy&lt;/a&gt; (&lt;abbr&gt;CSP&lt;/abbr&gt;) on this here Web Site&amp;trade;, I decided to just get rid of Google Analytics all together. I didn&amp;#39;t care about the stats, since I barely invested any time into this site. Moreover, much like &lt;a href=&quot;https://daverupert.com/2019/04/goodbye-google-analytics-hello-fathom/&quot;&gt;Dave&lt;/a&gt;, Google&amp;#39;s data collection practices creeped me out.&lt;/p&gt;
&lt;p&gt;But now that I am putting time and energy back into this site, my need to know if anyone is out there has come back, but thankfully it&amp;#39;s found a new tool: &lt;a href=&quot;https://usefathom.com&quot;&gt;Fathom Analytics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s what makes me happy about Fathom:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It was really easy to set up. As I&amp;#39;m already a DigitalOcean customer, having Fathom available as 1-click-install option for a new droplet made this a breeze to set up. I did try to install it on an existing droplet, and I decided that the one &lt;code&gt;502 Bad Gateway&lt;/code&gt; hiccup I ran into made it worth looking at spinning up an extra droplet for $5/month. I still don&amp;#39;t know why I was getting that &lt;code&gt;502&lt;/code&gt; error, but there was a bonus to going the &amp;quot;new droplet&amp;quot; route: it motivated me to remove some old droplets I didn&amp;#39;t need anymore, which means I&amp;#39;m now &lt;em&gt;saving money&lt;/em&gt;. But, of course, &lt;abbr&gt;YMMV&lt;/abbr&gt; on that.&lt;/li&gt;
&lt;li&gt;It plays well with my Content Security Policy. If a third-party resource even hints at requiring a significant change to my CSP, it&amp;#39;s immediately on the chopping block. I&amp;#39;m proud of my &lt;a href=&quot;https://observatory.mozilla.org&quot;&gt;Mozilla Observatory&lt;/a&gt; score and what that score means for anyone who visits this site, and I assure you there aren&amp;#39;t many things, if any, that would make me scale that back.&lt;/li&gt;
&lt;li&gt;It is essentials-only. Page views, unique visitors, bounce rate, time on site. I&amp;#39;d be a liar if I said I wasn&amp;#39;t curious about visitors&amp;#39; technology stats, like browser and maybe OS, but I don&amp;#39;t &lt;em&gt;need&lt;/em&gt; that information.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, if you&amp;#39;re reading this, know that I know that &lt;em&gt;someone&lt;/em&gt; read it, and that&amp;#39;s about all I know.&lt;/p&gt;
&lt;p&gt;And all I want to know.&lt;/p&gt;

							
						
					
				</content>
			</entry>
		
	
</feed>
