<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Dan Cătălin Burzo: Articles</title>
	<link href="https://danburzo.ro/feeds/articles.xml" rel="self" />
	<link href="https://danburzo.ro" />
	<updated
		>2025-12-22T14:01:58Z</updated
	>
	<id>https://danburzo.ro/</id>
	<author>
		<name>Dan Burzo</name>
	</author>
		
	<entry>
		<title>HTTP caching, a refresher</title>
		<link href="https://danburzo.ro/http-caching-refresher/" />
		<updated>2025-12-22T14:01:58Z</updated>
		<id>https://danburzo.ro/http-caching-refresher/</id>
		<content type="html"
			>&lt;p&gt;This is a fresh reading of &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111&quot;&gt;&lt;abbr&gt;RFC 9111&lt;/abbr&gt;&lt;/a&gt; (2022), the latest iteration of the &lt;abbr&gt;HTTP&lt;/abbr&gt; Caching standard.&lt;/p&gt;
&lt;p&gt;The standard defines the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control&quot;&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt; &lt;abbr&gt;HTTP&lt;/abbr&gt; header as a way to prescribe how caches should store and reuse &lt;abbr&gt;HTTP&lt;/abbr&gt; responses, with regard to not just the browser cache, but to any other intermediary caches, such as proxies and content delivery networks, that may exist between the client and the origin server.&lt;/p&gt;
&lt;img src=&quot;https://danburzo.ro/img/http-caching-refresher/cache-chain.png&quot; alt=&quot;A crude illustration depicting a browser with its private cache, two intermediary services with their shared caches, and the origin server&quot; width=&quot;1000&quot; height=&quot;310&quot; /&gt;
&lt;p&gt;The &lt;code&gt;Cache-Control&lt;/code&gt; header accepts a set of comma-separated directives, some of which are meant to be added to &lt;abbr&gt;HTTP&lt;/abbr&gt; requests, and others to &lt;abbr&gt;HTTP&lt;/abbr&gt; responses. A typical response header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/2 200
Cache-Control: max-age=0, must-revalidate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some of these directives specifically target &lt;em&gt;shared caches&lt;/em&gt;, that is intermediary caches that serve the same cached responses to many users, while others also apply to &lt;em&gt;private caches&lt;/em&gt; such as the browser cache.&lt;/p&gt;
&lt;p&gt;The &lt;abbr&gt;RFC&lt;/abbr&gt; is written from the perspective that &lt;abbr&gt;HTTP&lt;/abbr&gt; caching is desirable and widely on by default. It saves the strongest prescriptions — &lt;strong class=&quot;sc&quot;&gt;MUST&lt;/strong&gt; and &lt;strong class=&quot;sc&quot;&gt;MUST NOT&lt;/strong&gt; — for mechanisms meant to keep things &lt;em&gt;out&lt;/em&gt; of conforming caches, or to prevent these caches from reusing their stored responses inappropriately, rather than to force caches to store certain responses.&lt;/p&gt;
&lt;p&gt;Before &lt;abbr&gt;HTTPS&lt;/abbr&gt;-everywhere became the norm, a client’s communication to the server could be mediated by various proxies, deployed by your internet service provider or your workplace, that could automagically cache and optimize content on the fly. Some &lt;code&gt;Cache-Control&lt;/code&gt; directives were originally meant to rein in some of these proxy behaviors that were otherwise out of the control of clients or origin servers.&lt;/p&gt;
&lt;p&gt;These days, a much more relevant form of shared cache is the service provided by &lt;abbr&gt;CDN&lt;/abbr&gt;s, which act as gateways to ease the load on an origin server. &lt;abbr&gt;CDN&lt;/abbr&gt;s act as ‘conformant-with-footnotes’ &lt;abbr&gt;HTTP&lt;/abbr&gt; shared caches, meaning that they’ve adopted, to the extent they are able or willing, the &lt;abbr&gt;HTTP&lt;/abbr&gt; Caching rules as a vendor-agnostic way to configure their behavior. But unlike the ‘web accelerators’ of yore, they don’t need to rely on &lt;abbr&gt;HTTP&lt;/abbr&gt; headers alone, as server owners can often supplement them with ‘out-of-band’ configuration that enhances, or overrides, &lt;abbr&gt;HTTP&lt;/abbr&gt; semantics. Other shared caches, such as &lt;a href=&quot;https://vinyl-cache.org/&quot;&gt;Vinyl &lt;abbr&gt;HTTP&lt;/abbr&gt; Cache&lt;/a&gt; (formerly known as Varnish), are typically installed closer to an origin server and have similar configuration affordances.&lt;/p&gt;
&lt;p&gt;So &lt;abbr&gt;HTTP&lt;/abbr&gt; caching remains as relevant as ever.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No cache no chill&lt;/strong&gt;. Besides browsers, other types of web clients we don’t usually think of as incorporating a private cache — think artisanal &lt;abbr&gt;RSS&lt;/abbr&gt; feed readers, web scrapers, &lt;abbr&gt;URL&lt;/abbr&gt; unfurlers — could, and should, become fluent in cache-talk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What’s fresh?&lt;/h2&gt;
&lt;p&gt;Whenever the cache receives a request, it must figure out if the cached response is still &lt;strong&gt;fresh&lt;/strong&gt; and can therefore be reused without incurring the performance tax of an &lt;abbr&gt;HTTP&lt;/abbr&gt; request, or whether it has gone &lt;strong&gt;stale&lt;/strong&gt; and should be validated with the server.&lt;/p&gt;
&lt;p&gt;To decide on freshness, the cache compares the age of the response to the response’s so-called freshness timeline.&lt;/p&gt;
&lt;p&gt;The age of a cached response is the time elapsed since it was last generated or revalidated by the origin server. To the time spent in its own cache, the browser will add any &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Age&quot;&gt;&lt;code&gt;Age: &amp;lt;seconds&amp;gt;&lt;/code&gt;&lt;/a&gt; header received from intermediary caches.&lt;/p&gt;
&lt;p&gt;The freshness timeline is a duration beyond which the cached response is to be considered stale. It’s usually signaled by the server via the appropriate response headers, but may also be guesstimated by the cache in the absence of explicit, valid cues. In order of precedence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the server establishes a freshness timeline, in seconds, with the &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#max-age-response&quot;&gt;&lt;code&gt;Cache-Control: max-age=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/a&gt; directive on the response; otherwise,&lt;/li&gt;
&lt;li&gt;the cache falls back to computing the interval between the &lt;code&gt;Expires: &amp;lt;date&amp;gt;&lt;/code&gt; and &lt;code&gt;Date: &amp;lt;date&amp;gt;&lt;/code&gt; response headers, if available; otherwise,&lt;/li&gt;
&lt;li&gt;if there’s no &lt;code&gt;Expires&lt;/code&gt; header, the response lacks an explicit expiration, and a heuristic freshness based on the &lt;code&gt;Last-Modified&lt;/code&gt; response header might be applicable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For shared caches, the special &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#s-maxage-response&quot;&gt;&lt;code&gt;s-maxage=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/a&gt; directive takes precedence over all others.&lt;/p&gt;
&lt;h2&gt;Going past expiration&lt;/h2&gt;
&lt;p&gt;Just because a response has gone stale, it doesn’t mean it needs to be thrown out.&lt;/p&gt;
&lt;p&gt;When it receives a request for a stale cached response, the cache should validate it with its upstream server. Although validation always generates an &lt;abbr&gt;HTTP&lt;/abbr&gt; request, it avoids a data transfer when there’s no newer version of the cached response on the server, so it can still be faster than a regular request.&lt;/p&gt;
&lt;p&gt;Validation uses a mechanism known as a conditional &lt;abbr&gt;HTTP&lt;/abbr&gt; request, which includes one or more special headers called preconditions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the precondition with the highest precedence is met, the server responds with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200&quot;&gt;&lt;abbr&gt;HTTP 200 OK&lt;/abbr&gt;&lt;/a&gt; and an updated response body; otherwise,&lt;/li&gt;
&lt;li&gt;it responds with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304&quot;&gt;&lt;abbr&gt;HTTP 304&lt;/abbr&gt; Not Modified&lt;/a&gt; and an empty body, confirming the existing response can be reused.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To generate the preconditions needed for these conditional requests, which the server uses to compare the cached response to the freshest version available, responses must be tagged in a way that’s unique to each version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;historically, this was done with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified&quot;&gt;&lt;code&gt;Last-Modified: &amp;lt;date&amp;gt;&lt;/code&gt;&lt;/a&gt; header, corresponding to the latest update to the content;&lt;/li&gt;
&lt;li&gt;a more flexible and robust alternative is the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag&quot;&gt;&lt;code&gt;ETag: &amp;quot;&amp;lt;value&amp;gt;&amp;quot;&lt;/code&gt;&lt;/a&gt; header, which stores an arbitrary &lt;abbr&gt;ASCII&lt;/abbr&gt; string that uniquely identifies the response. This string is usually a hash incorporating one or more aspects: the modification time, the file size, and the file content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When performing the validation, the cached response headers are mirrored as preconditions for the conditional request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Last-Modified: &amp;lt;date&amp;gt;&lt;/code&gt; becomes &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since&quot;&gt;&lt;code&gt;If-Modified-Since: &amp;lt;date&amp;gt;&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ETag: &amp;quot;&amp;lt;value&amp;gt;&amp;quot;&lt;/code&gt; becomes &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match&quot;&gt;&lt;code&gt;If-None-Match: &amp;quot;&amp;lt;value&amp;gt;&amp;quot;&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When both preconditions are present, only &lt;code&gt;If-None-Match&lt;/code&gt; is evaluated.&lt;/p&gt;
&lt;p&gt;Regardless of the result of the validation request, the cached response headers are updated with the new values received from the server, and the fresh-o-meter on the cached response is reset.&lt;/p&gt;
&lt;p&gt;Certain caches may be set up to serve stale responses in some circumstances, such as when losing the connection to the server or in the event of an &lt;abbr&gt;HTTP 5xx&lt;/abbr&gt; server error. There are also &lt;code&gt;Cache-Control&lt;/code&gt; directives that influence how stale responses are handled, covered in the next section.&lt;/p&gt;
&lt;h2&gt;How caches store responses&lt;/h2&gt;
&lt;p&gt;An &lt;abbr&gt;HTTP&lt;/abbr&gt; cache stores previous responses under &lt;em&gt;cache keys&lt;/em&gt;, and uses those keys when looking for suitable cached responses to satisfy new requests. At a minimum, the cache key is composed of the &lt;abbr&gt;URL&lt;/abbr&gt; and &lt;abbr&gt;HTTP&lt;/abbr&gt; method used to retrieve the response.&lt;/p&gt;
&lt;p&gt;Browsers further &lt;a href=&quot;https://developer.chrome.com/blog/http-cache-partitioning&quot;&gt;partition cached responses&lt;/a&gt; by &lt;em&gt;where&lt;/em&gt; the &lt;abbr&gt;URL&lt;/abbr&gt; is requested from. This is called &lt;em&gt;double-keying&lt;/em&gt;, and helps protect user privacy. With an eye out for how &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; elements can be abused to track users across websites, &lt;a href=&quot;https://github.com/whatwg/fetch/issues/1035#issuecomment-2658239821&quot;&gt;this additional key&lt;/a&gt; may include, depending on the browser, the top-level site, the frame site, and other boolean flags.&lt;/p&gt;
&lt;h3&gt;Variable responses and the &lt;code&gt;Vary&lt;/code&gt; response header&lt;/h3&gt;
&lt;p&gt;For the same requested &lt;abbr&gt;URL&lt;/abbr&gt;, the server may return different representations depending on preferences expressed by the client through &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields&quot;&gt;dedicated request headers&lt;/a&gt; — &lt;code&gt;Accept&lt;/code&gt;, &lt;code&gt;Accept-Charset&lt;/code&gt;, &lt;code&gt;Accept-Encoding&lt;/code&gt;, &lt;code&gt;Accept-Language&lt;/code&gt;, and the experimental &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Client_hints&quot;&gt;Client Hints&lt;/a&gt; — in a process known as &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9110#content.negotiation&quot;&gt;content negotiation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Likewise, for responses meant for individual users, the server may return different representations of a &lt;abbr&gt;URL&lt;/abbr&gt; depending on the value of the &lt;code&gt;Authorization&lt;/code&gt; header, and in some cases &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#caching-authenticated-responses&quot;&gt;the responses are cacheable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Storing the different representations of a &lt;abbr&gt;URL&lt;/abbr&gt; under a single cache key would be inadvisable, since the representations are not equivalent and shouldn’t be reused. This is addressed with the &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-vary&quot;&gt;&lt;code&gt;Vary&lt;/code&gt; response header&lt;/a&gt;, which the server can use to list the request headers whose value may have played a role in selecting the response content.&lt;/p&gt;
&lt;figure&gt;
&lt;pre&gt;&lt;code&gt;HTTP/2 200
Vary: accept-language

Bonjour!
&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;The &lt;code&gt;Vary&lt;/code&gt; header in this response states that the &lt;code&gt;Accept-Language&lt;/code&gt; request header influenced the response message.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When a response contains a &lt;code&gt;Vary&lt;/code&gt; header, the cache must factor into its cache key the values of the nominated request headers, so that the response is &lt;em&gt;generally&lt;/em&gt; only ever reused for requests whose values for these headers match. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-calculating-cache-keys-with&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 4.1&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Why ‘generally’? Because the &lt;abbr&gt;RFC&lt;/abbr&gt; discourages, but technically allows, caches to essentially repurpose stored responses for the same &lt;abbr&gt;URL&lt;/abbr&gt;, even with mismatching &lt;code&gt;Vary&lt;/code&gt; values, if they are successfully revalidated for the new conditions. It was a &lt;a href=&quot;https://github.com/httpwg/http-core/issues/832&quot;&gt;decision&lt;/a&gt; meant to maintain compatibility with the previous iteration of &lt;abbr&gt;HTTP&lt;/abbr&gt; core standards, and avoid flagging as non-conformant cache behavior in Chrome and Firefox: both browsers include &lt;code&gt;Etags&lt;/code&gt; from mismatched responses as validation request preconditions.&lt;/p&gt;
&lt;p&gt;Wildcards are also allowed. A &lt;code&gt;Vary: *&lt;/code&gt; response header indicates that factors outside of the request message were taken into consideration for producing the response, so the cache must always validate a stored response with the server before reusing it.&lt;/p&gt;
&lt;h3&gt;Your mileage may vary&lt;/h3&gt;
&lt;p&gt;As defined, &lt;code&gt;Vary&lt;/code&gt; is not without its problems. When it nominates request headers with high variability, the &lt;code&gt;Vary&lt;/code&gt; header can generate very inefficient cache usage, with bucketloads of non-reusable variants in storage.&lt;/p&gt;
&lt;p&gt;Some &lt;abbr&gt;CDN&lt;/abbr&gt;s, such as Fastly, automatically &lt;a href=&quot;https://www.fastly.com/documentation/reference/http/http-headers/Accept-Encoding/#normalization&quot;&gt;normalize problematic headers&lt;/a&gt; to a more manageable set. Other services may &lt;a href=&quot;https://simonwillison.net/2023/Nov/20/cloudflare-does-not-consider-vary-values-in-caching-decisions/&quot;&gt;ignore the &lt;code&gt;Vary&lt;/code&gt; header&lt;/a&gt; or deem such responses &lt;a href=&quot;https://techdocs.akamai.com/property-mgr/docs/rm-vary-header&quot;&gt;uncacheable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A number of solutions to complement or replace &lt;code&gt;Vary&lt;/code&gt; were explored, among which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-key&quot;&gt;&lt;code&gt;Key&lt;/code&gt; response header&lt;/a&gt;, letting the origin server declare how secondary cache keys for variants are to be generated;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-variants&quot;&gt;&lt;abbr&gt;HTTP&lt;/abbr&gt; Response Variants&lt;/a&gt;, laying the groundwork for clients and intermediary caches to negociate content without necessarily checking in with the origin server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both approaches were ultimately shelved for reasons of complexity, developer-friendliness, or limitated applicability. The latest proposal, &lt;a href=&quot;https://mnot.github.io/I-D/draft-nottingham-http-availability-hints.html&quot;&gt;&lt;abbr&gt;HTTP&lt;/abbr&gt; Availability Hints&lt;/a&gt; — &lt;q&gt;a new class of &lt;abbr&gt;HTTP&lt;/abbr&gt; responses headers that augment the information in the &lt;code&gt;Vary&lt;/code&gt; header field&lt;/q&gt; — aims to address some of these shortcomings.&lt;/p&gt;
&lt;h3&gt;Deduplicating responses with the &lt;code&gt;No-Vary-Search&lt;/code&gt; response header&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;Vary&lt;/code&gt; is about storing separately distinct representations that would otherwise be grouped under the same cache key, the &lt;a href=&quot;https://httpwg.org/http-extensions/draft-ietf-httpbis-no-vary-search.html&quot;&gt;&lt;code&gt;No-Vary-Search&lt;/code&gt; &lt;abbr&gt;HTTP&lt;/abbr&gt; Response Header Field&lt;/a&gt; proposal addresses the opposite problem: essentially identical content that’s cached several times over due to inconsequential variations in its &lt;abbr&gt;URL&lt;/abbr&gt;.&lt;/p&gt;
&lt;p&gt;With the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/No-Vary-Search&quot;&gt;&lt;code&gt;No-Vary-Seach&lt;/code&gt; header&lt;/a&gt;, the server can declare that some or all of the &lt;abbr&gt;URL&lt;/abbr&gt; query parameters don’t affect the response, meaning they can be ignored when generating the cache key to &lt;a href=&quot;https://calendar.perfplanet.com/2025/fixing-the-url-params-performance-penalty/&quot;&gt;improve cache performance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At this time, the &lt;code&gt;No-Vary-Search&lt;/code&gt; header is only implemented in Chromium-based browsers.&lt;/p&gt;
&lt;h2&gt;Cache-Control response directives&lt;/h2&gt;
&lt;h3 id=&quot;max-age-response&quot;&gt;
&lt;p&gt;&lt;code&gt;max-age=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;max-age&lt;/code&gt; response directive defines the response’s freshness timeline in seconds, after which the response should be considered stale. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-max-age-2&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.1&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;must-revalidate-response&quot;&gt;
&lt;p&gt;&lt;code&gt;must-revalidate&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;must-revalidate&lt;/code&gt; response directive indicates that the cache must not reuse a stale response until it’s been successfully validated by the origin server. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-must-revalidate&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.2&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If the server throws an error, the cache must surface that instead of reusing a stale response. If the cache is disconnected, it must produce an error with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/504&quot;&gt;&lt;abbr&gt;HTTP 504&lt;/abbr&gt; Gateway Timeout&lt;/a&gt; status code, or another more applicable error code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Side effects:&lt;/strong&gt; &lt;code&gt;must-revalidate&lt;/code&gt; is one of the directives, along with &lt;code&gt;s-maxage&lt;/code&gt; and &lt;code&gt;public&lt;/code&gt;, that allow shared caches to &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#caching-authenticated-responses&quot;&gt;store and reuse a response to a request containing an &lt;code&gt;Authorization&lt;/code&gt; header&lt;/a&gt;, which they are generally prohibited from doing.&lt;/p&gt;
&lt;h3 id=&quot;no-cache-response&quot;&gt;
&lt;p&gt;&lt;code&gt;no-cache&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;no-cache&lt;/code&gt; response directive indicates that the cache must not reuse &lt;em&gt;any&lt;/em&gt; response until it’s successfully validated by the origin server. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-no-cache-2&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.4&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is similar to &lt;code&gt;must-revalidate&lt;/code&gt; but refers to all cached responses, not just stale ones. In effect, &lt;code&gt;no-cache&lt;/code&gt; is a sort of &lt;code&gt;max-age=0, must-revalidate&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;no-store-reponse&quot;&gt;
&lt;p&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;no-store&lt;/code&gt; response directive indicates that private and shared caches must not store any part of the request or the response, and to never reuse the response.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“&lt;em&gt;&lt;span class=&quot;sc&quot;&gt;MUST NOT&lt;/span&gt; store&lt;/em&gt; in this context means that the cache &lt;span class=&quot;sc&quot;&gt;MUST NOT&lt;/span&gt; intentionally store the information in non-volatile storage and &lt;span class=&quot;sc&quot;&gt;MUST&lt;/span&gt; make a best-effort attempt to remove the information from volatile storage as promptly as possible after forwarding it. This directive is not a reliable or sufficient mechanism for ensuring privacy. In particular, malicious or compromised caches might not recognize or obey this directive, and communications networks might be vulnerable to eavesdropping.” &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-no-store-2&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.4&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Side effects:&lt;/strong&gt; The directive can also influence non-&lt;abbr&gt;HTTP&lt;/abbr&gt; caches. Most browsers will &lt;a href=&quot;https://web.dev/articles/bfcache#minimize-no-store&quot;&gt;exclude from the back/forward cache&lt;/a&gt; pages having the &lt;code&gt;no-store&lt;/code&gt; response directive. Chrome, however, has recently started to make &lt;em&gt;some&lt;/em&gt; of these pages &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/bfcache-ccns&quot;&gt;eligible for bfcache&lt;/a&gt; when the browser deems it safe.&lt;/p&gt;
&lt;h3 id=&quot;must-understand-response&quot;&gt;
&lt;p&gt;&lt;code&gt;must-understand&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;must-understand&lt;/code&gt; response directive indicates that the cache shouldn’t store or reuse responses with &lt;abbr&gt;HTTP&lt;/abbr&gt; status codes whose semantics the cache doesn’t understand and conform to. The directive is meant to future-proof existing implementations from status codes that might have special requirements in regards to caching. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-must-understand&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.3&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s recommended to use &lt;code&gt;must-understand, no-store&lt;/code&gt; together as a fallback, and caches are encouraged to ignore the &lt;code&gt;no-store&lt;/code&gt; directive if they do understand the semantics of the &lt;abbr&gt;HTTP&lt;/abbr&gt; status code. This ensures older caches that don’t recognize the &lt;code&gt;must-understand&lt;/code&gt; directive don’t cache the response at all, although by 2025 this should be an exceedingly rare sight.&lt;/p&gt;
&lt;h3 id=&quot;private-response&quot;&gt;
&lt;p&gt;&lt;code&gt;private&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;private&lt;/code&gt; response directive indicates that the response is meant for a single user. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-private&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.7&lt;/abbr&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Therefore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a shared cache must not store the response; and&lt;/li&gt;
&lt;li&gt;a private cache may store the response even if the response wouldn’t otherwise be heuristically cacheable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;private&lt;/code&gt; directive can be used to guard against other directives that might inadvertently make &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#caching-authenticated-responses&quot;&gt;authenticated responses available to shared caches&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;public-response&quot;&gt;
&lt;p&gt;&lt;code&gt;public&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;public&lt;/code&gt; response directive indicates two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a shared cache may &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#caching-authenticated-responses&quot;&gt;store and reuse a response to a request containing an &lt;code&gt;Authorization&lt;/code&gt; header&lt;/a&gt;, which it’s generally prohibited from doing; and&lt;/li&gt;
&lt;li&gt;a private cache may store the response even if the response wouldn’t otherwise be heuristically cacheable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-public&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.9&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;s-maxage-response&quot;&gt;
&lt;p&gt;&lt;code&gt;s-maxage=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;s-maxage=&amp;lt;number&amp;gt;&lt;/code&gt; response directive is analogous to &lt;code&gt;max-age&lt;/code&gt;, but only affects shared caches. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-s-maxage&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.10&lt;/abbr&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The directive also incorporates the semantics of the &lt;code&gt;proxy‑revalidate&lt;/code&gt; response directive, in that a shared cache must not use a stale response until it has been successfully validated with the origin server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Side effects:&lt;/strong&gt; &lt;code&gt;s-maxage&lt;/code&gt; is one of the directives, along with &lt;code&gt;must-revalidate&lt;/code&gt; and &lt;code&gt;public&lt;/code&gt;, that allow shared caches to &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#caching-authenticated-responses&quot;&gt;store and reuse a response to a request containing an &lt;code&gt;Authorization&lt;/code&gt; header&lt;/a&gt;, which they are generally prohibited from doing.&lt;/p&gt;
&lt;h3 id=&quot;proxy-revalidate-response&quot;&gt;
&lt;p&gt;&lt;code&gt;proxy-revalidate&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;proxy-revalidate&lt;/code&gt; response directive is analogous to &lt;code&gt;must-revalidate&lt;/code&gt;, but only affects shared caches. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-proxy-revalidate&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.8&lt;/abbr&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;no-transform-response&quot;&gt;
&lt;p&gt;&lt;code&gt;no-transform&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;no-transform&lt;/code&gt; response directive indicates that intermediaries, regardless of whether they implement a cache or not, must not transform the response content, such as optimizing images or compressing stylesheets and scripts. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-no-transform-2&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.2.6&lt;/abbr&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;stale-while-revalidate-response&quot;&gt;
&lt;p&gt;&lt;code&gt;stale-while-revalidate=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;stale-while-revalidate&lt;/code&gt; response directive was defined in &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc5861&quot;&gt;&lt;abbr&gt;RFC 5861&lt;/abbr&gt;: &lt;abbr&gt;HTTP&lt;/abbr&gt; Cache-Control Extensions for Stale Content&lt;/a&gt; (2010). It indicates that the cache may use a cached response if it hasn’t exceeded its freshness lifetime by more than the specified number of seconds.&lt;/p&gt;
&lt;p&gt;Whenever the presence of this directive causes a stale response to be served, the cache should trigger a background revalidation of the response.&lt;/p&gt;
&lt;p&gt;Mark Nottingham, the author of &lt;abbr&gt;RFC 5861&lt;/abbr&gt;, &lt;a href=&quot;https://www.mnot.net/blog/2014/06/01/chrome_and_stale-while-revalidate&quot;&gt;has written a rationale&lt;/a&gt; for this directive.&lt;/p&gt;
&lt;h3 id=&quot;stale-if-error-response&quot;&gt;
&lt;p&gt;&lt;code&gt;stale-if-error=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;p&gt;Also defined in the &lt;abbr&gt;RFC 5861&lt;/abbr&gt; extension, the &lt;code&gt;stale-if-error&lt;/code&gt; response directive indicates that the cache may use a cached response if it hasn’t exceeded its freshness lifetime by more than the specified number of seconds, if the attempt to validate the stale response results in an error.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cache-tests.fyi/&quot;&gt;&lt;abbr&gt;HTTP&lt;/abbr&gt; Caching Tests&lt;/a&gt; suggests this directive is not well supported.&lt;/p&gt;
&lt;h2&gt;Cache-Control request directives&lt;/h2&gt;
&lt;p&gt;As web developers, we most often deal with &lt;code&gt;Cache-Control&lt;/code&gt; in &lt;abbr&gt;HTTP&lt;/abbr&gt; responses, but this header can also be included on &lt;abbr&gt;HTTP&lt;/abbr&gt; requests. Browsers, for example, use them when the user refreshes the page.&lt;/p&gt;
&lt;p&gt;When used in &lt;abbr&gt;HTTP&lt;/abbr&gt; requests, &lt;code&gt;Cache-Control&lt;/code&gt; directives express the client’s preference in regards to the freshness or age of the response. Caches reconcile these requests with the &lt;code&gt;Cache-Control&lt;/code&gt; response directives of its cached responses.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#cache&quot;&gt;&lt;code&gt;cache&lt;/code&gt; option&lt;/a&gt; for &lt;code&gt;fetch()&lt;/code&gt; has a separate set of values that map to &lt;code&gt;Cache-Control&lt;/code&gt; request directives, but the mappings are not always intuitive. For example, &lt;code&gt;cache: &#39;no-cache&#39;&lt;/code&gt; maps to &lt;code&gt;Cache-Control: max-age=0&lt;/code&gt;. For the curious, the mappings are &lt;a href=&quot;https://fetch.spec.whatwg.org/#http-network-or-cache-fetch&quot;&gt;defined here&lt;/a&gt;. You can always set your &lt;code&gt;Cache-Control&lt;/code&gt; headers directly with the &lt;code&gt;headers&lt;/code&gt; option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;&lt;code&gt;max-age=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;max-age&lt;/code&gt; request directive indicates that the client wants a fresh response whose age is less than or equal to the specified number of seconds. When combined with &lt;code&gt;max-stale&lt;/code&gt;, the client will accept some stale responses. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-max-age&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.1&lt;/abbr&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;max-stale=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;max-stale&lt;/code&gt; request directive indicates that the client will accept a stale response that has exceeded its freshness lifetime by no more than the specified number of seconds. When used without an argument, &lt;code&gt;max-stale&lt;/code&gt; indicates that the client will accept any stale response, no matter how old. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#section-5.2.1.2&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.2&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;min-fresh=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;min-fresh&lt;/code&gt; request directive indicates that the client prefers a response that still has at least the specified number of seconds of freshness left. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-min-fresh&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.3&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;no-cache&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;no-cache&lt;/code&gt; request directive indicates that the client prefers caches not to use a stored response without successfully validating it with the origin server. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-no-cache&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.4&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;no-store&lt;/code&gt; request directive indicates that a cache must not store any part of either this request or any response to it. The same caveats as to &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#no-store-response&quot;&gt;its response counterpart&lt;/a&gt; apply. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-no-store&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.5&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If a cache serves this request with a response that was previously stored, the &lt;code&gt;no-store&lt;/code&gt; request directive doesn’t cause the cache to remove the response after serving it.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;no-transform&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;no-transform&lt;/code&gt; request directive indicates that the client is asking for intermediaries to avoid transforming the content. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-no-transform&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.6&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;only-if-cached&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;only-if-cached&lt;/code&gt; request directive indicates that the client only wants a stored response. Caches should respond with either a stored response that satisfies all the other constraints, or an &lt;abbr&gt;HTTP 504&lt;/abbr&gt; Gateway Timeout status code. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-only-if-cached&quot;&gt;☞ &lt;abbr&gt;RFC 9111 § 5.2.1.7&lt;/abbr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;stale-if-error=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Similarly to &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#stale-if-error-response&quot;&gt;its response counterpart&lt;/a&gt;, the &lt;code&gt;stale-if-error&lt;/code&gt; request directive indicates that the client will accept a stale response that has exceeded its freshness lifetime by no more than the specified number of seconds, if an attempt to validate it resulted in a server error.&lt;/p&gt;
&lt;h2&gt;Browser refresh mechanisms&lt;/h2&gt;
&lt;p&gt;Browsers typically offer two refresh mechanisms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;soft reloads&lt;/em&gt;, triggered by the reload button, a corresponding menu item and keyboard shortcut, and the pull-to-refresh gesture in mobile browsers, are meant to get an updated representation of the page, for example getting the latest posts on a social media timeline.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;hard reloads&lt;/em&gt;, enabled with a modifier key, skip the cache altogether and are meant to fix interrupted loads, outdated cached responses, and other broken states.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s how some browsers on macOS implement these behaviors.&lt;/p&gt;
&lt;h3&gt;Soft reloads&lt;/h3&gt;
&lt;p&gt;Triggered by &lt;kbd&gt;Ctrl + R&lt;/kbd&gt; on Windows/Linux and &lt;kbd&gt;Command + R&lt;/kbd&gt; on macOS.&lt;/p&gt;
&lt;p&gt;Firefox triggers a conditional request to revalidate the cached response for the main resource (the &lt;abbr&gt;HTML&lt;/abbr&gt; file). Sub-resources such as stylesheets, scripts, and images are reloaded as usual, according to their cache directives.&lt;/p&gt;
&lt;p&gt;Chrome behaves similarly, with the difference that the validation request for the main resource also includes a &lt;code&gt;Cache-Control: max-age=0&lt;/code&gt; directive (which can’t hurt).&lt;/p&gt;
&lt;p&gt;Instead of revalidating its cached response, Safari performs a non-conditional request for the main resource, then loads sub-resources as usual.&lt;/p&gt;
&lt;h3&gt;Hard reloads&lt;/h3&gt;
&lt;p&gt;Triggered by &lt;kbd&gt;Ctrl + Shift + R&lt;/kbd&gt; on Windows/Linux and &lt;kbd&gt;Command + Shift + R&lt;/kbd&gt; on macOS except Safari, which uses &lt;kbd&gt;Command + Option + R&lt;/kbd&gt;. (If you’ve applied your muscle memory to Safari before, you know all too well that the common shortcut opens Reader Mode instead…)&lt;/p&gt;
&lt;p&gt;On a hard reload, all three browsers trigger non-conditional requests with the &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; directive on the &lt;abbr&gt;HTML&lt;/abbr&gt; page and its sub-resources.&lt;/p&gt;
&lt;p&gt;Curiously, once you perform a hard reload in Safari, subsequent soft reloads will still use the &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; request directive to fetch the main resource, which is probably an unintended, but otherwise benign behavior.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;immutable&lt;/code&gt; response directive&lt;/h3&gt;
&lt;p&gt;Reloading a web page didn’t always work like this. Historically, when performing a soft reload, all the sub-resources would be revalidated along with the main resource, in effect freshening up the cache for the current page.&lt;/p&gt;
&lt;p&gt;Circa 2015, Facebook was seeing several &lt;abbr&gt;HTTP 304&lt;/abbr&gt; Not Modified responses on long-lived resources like scripts and stylesheets whenever a user would refresh their feed page with the browser’s reload button.&lt;/p&gt;
&lt;p&gt;To address this issue, Patrick McManus from Mozilla &lt;a href=&quot;https://bitsup.blogspot.com/2016/05/cache-control-immutable.html&quot;&gt;proposed&lt;/a&gt; the &lt;code&gt;immutable&lt;/code&gt; response directive, which later became &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8246&quot;&gt;&lt;abbr&gt;RFC 8246&lt;/abbr&gt;: &lt;abbr&gt;HTTP&lt;/abbr&gt; Immutable Responses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The directive indicates that the origin server won’t update a resource during the freshness lifetime of the cached response, so a cache shouldn’t issue conditional requests for responses that are still fresh when the user reloads the page, unless the user really, really wants an updated response (e.g. a hard reload).&lt;/p&gt;
&lt;p&gt;Around the time that &lt;a href=&quot;https://hacks.mozilla.org/2017/01/using-immutable-caching-to-speed-up-the-web/&quot;&gt;support for &lt;code&gt;immutable&lt;/code&gt;&lt;/a&gt; landed in Firefox 49 and Facebook began to use it to great effect, Chrome introduced a &lt;a href=&quot;https://blog.chromium.org/2017/01/reload-reloaded-faster-and-leaner-page_26.html&quot;&gt;new way to perform reloads&lt;/a&gt; that solved the problem without introducing additional directives: instead of revalidating everything on a soft reload, just revalidate the main resource and load sub-resources as usual. Safari switched over to the new reload policy soon after [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=169756&quot;&gt;Webkit#169756&lt;/a&gt;], and Firefox eventually did with &lt;a href=&quot;https://www.firefox.com/en-US/firefox/100.0/releasenotes/&quot;&gt;Firefox 100&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That leaves the &lt;code&gt;immutable&lt;/code&gt; directive in an awkward place. Safari added support [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=167497&quot;&gt;Webkit#167497&lt;/a&gt;] but Chrome representatives remain unconvinced that it offers a significant benefit on top of the current reload behavior [&lt;a href=&quot;https://issues.chromium.org/issues/41253661&quot;&gt;Chromium#41253661&lt;/a&gt;].&lt;/p&gt;
&lt;h2 id=&quot;caching-authenticated-responses&quot;&gt;
Caching responses to authenticated requests
&lt;/h2&gt;
&lt;p&gt;One of the more confusing aspects of &lt;abbr&gt;HTTP&lt;/abbr&gt; caching is how various &lt;code&gt;Cache-Control&lt;/code&gt; response directives affect the way shared caches treat responses to requests that contain an &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-authorization&quot;&gt;&lt;code&gt;Authorization&lt;/code&gt; header&lt;/a&gt;, which are understood as specific to a single user.&lt;/p&gt;
&lt;p&gt;As per &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9111#name-storing-responses-to-authen&quot;&gt;&lt;abbr&gt;RFC 9111 § 3.5&lt;/abbr&gt;&lt;/a&gt;, shared caches are not allowed to store these responses &lt;q&gt;unless the response contains a Cache-Control field with a response directive that allows it to be stored by a shared cache, and the cache conforms to the requirements of that directive for that response.&lt;/q&gt;&lt;/p&gt;
&lt;p&gt;The three directives that enable shared caches to store authenticated responses, and which must therefore be carefully evaluated before deploying, are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#public-response&quot;&gt;&lt;code&gt;public&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#s-maxage-response&quot;&gt;&lt;code&gt;s-maxage=&amp;lt;number&amp;gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#must-revalidate-response&quot;&gt;&lt;code&gt;must-revalidate&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Conversely, a &lt;a href=&quot;https://danburzo.ro/http-caching-refresher/#private-response&quot;&gt;&lt;code&gt;private&lt;/code&gt;&lt;/a&gt; directive prevents any other directive from making authenticated responses eligible to shared caches.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I wrote this article to clarify for myself what the various cache directives stand for and how they overlap and interact. It only covers the main ideas, without delving into the more obscure corners of &lt;abbr&gt;HTTP&lt;/abbr&gt; semantics. I approached the subject with a “clear cache”, and mainly used the normative references (&lt;abbr&gt;RFC 9111&lt;/abbr&gt; and its extensions), aided by various guides from different eras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mnot.net/cache_docs/&quot;&gt;Caching Tutorial for Web Authors and Webmasters&lt;/a&gt; (1998—) by Mark Nottingham;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/2016/caching-best-practices/&quot;&gt;Caching best practices &amp;amp; max-age gotchas&lt;/a&gt; (2016) by Jake Archibald;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/http-cache&quot;&gt;Prevent unnecessary network requests with the &lt;abbr&gt;HTTP&lt;/abbr&gt; Cache&lt;/a&gt; (2018) by Ilya Grigorik and Jeff Posnik;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://csswizardry.com/2019/03/cache-control-for-civilians/&quot;&gt;Cache control for civilians&lt;/a&gt; (2019–2025) and &lt;a href=&quot;https://csswizardry.com/2025/03/why-do-we-have-a-cache-control-request-header/&quot;&gt;Why Do We Have a Cache-Control Request Header?&lt;/a&gt; (2025) by Harry Roberts;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://grayduck.mn/2021/09/13/cache-control-recommendations/&quot;&gt;Cache-Control Recommendations&lt;/a&gt; (2021) by April King;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching&quot;&gt;Web Caching&lt;/a&gt; on &lt;abbr&gt;MDN&lt;/abbr&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s interesting about these guides is that the recommendations don’t just encode an interpretation of the specs, but also incorporate safeguards against non-conformant or outdated browser caches and intermediares.&lt;/p&gt;
&lt;p&gt;In light of developments as recent as 2022, it would be cool to figure out to what extent things have improved, and which of these safeguards can be discarded. &lt;a href=&quot;https://cache-tests.fyi/&quot;&gt;&lt;abbr&gt;HTTP&lt;/abbr&gt; Caching Tests&lt;/a&gt; seems to be a good resource for assessing the situation.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2025</title>
		<link href="https://danburzo.ro/favorite-records-2025/" />
		<updated>2025-12-20T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2025/</id>
		<content type="html"
			>&lt;p&gt;This year I’ve listened to music for around 65,000 minutes, or about three hours daily. Here are 30 records I’ve played a lot, listed in alphabetical order.&lt;/p&gt;
&lt;ul class=&quot;records&quot;&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://annavonhausswolff.bandcamp.com/album/iconoclasts&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/anna-von-hausswolff-iconoclasts.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Iconoclasts&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Anna von Hausswolff&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://annavonhausswolff.bandcamp.com/album/iconoclasts&quot;&gt;
			Iconoclasts
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;span class=&quot;sc&quot;&gt;YEAR0001&lt;/span&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://baxterdury.bandcamp.com/album/allbarone&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/baxter-dury-allbarone.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Allbarone&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Baxter Dury&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://baxterdury.bandcamp.com/album/allbarone&quot;&gt;
			Allbarone
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Heavenly / &lt;abbr&gt;PIAS&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://bigthief.bandcamp.com/album/double-infinity&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/big-thief-double-infinity.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Double Infinity&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Big Thief&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://bigthief.bandcamp.com/album/double-infinity&quot;&gt;
			Double Infinity
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;abbr&gt;4AD&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://boniver.bandcamp.com/album/sable-fable&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/bon-iver-sable-fable.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of SABLE, fABLE&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Bon Iver&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://boniver.bandcamp.com/album/sable-fable&quot;&gt;
			SABLE, fABLE
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Jagjaguwar&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://burial.bandcamp.com/album/comafields-imaginary-festival&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/burial-comafields.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Comafields / Imaginary Festival&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Burial&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://burial.bandcamp.com/album/comafields-imaginary-festival&quot;&gt;
			Comafields / Imaginary Festival
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Hyperdub&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://danielavery.bandcamp.com/album/tremor&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/daniel-avery-tremor.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Tremor&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Daniel Avery&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://danielavery.bandcamp.com/album/tremor&quot;&gt;
			Tremor
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Domino&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://diewildejagd.bandcamp.com/album/lux-tenera-a-rite-to-joy&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/die-wilde-jagd-lux-tenera.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Lux Tenera: A Rite to Joy&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Die Wilde Jagd, Metropole Orkest&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://diewildejagd.bandcamp.com/album/lux-tenera-a-rite-to-joy&quot;&gt;
			Lux Tenera: A Rite to Joy
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Bureau B&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.deafheavenuk.com/&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/deafheaven-lonely-people-with-power.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Lonely People With Power&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Deafheaven&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.deafheavenuk.com/&quot;&gt;
			Lonely People With Power
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Roadrunner&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://rachika.bandcamp.com/album/disiniblud&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/disiniblud-disiniblud.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Disiniblud&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Disiniblud (Rachika Nayar, Nina Keith)&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://rachika.bandcamp.com/album/disiniblud&quot;&gt;
			Disiniblud
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Smugglers Way&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://dovesofficial.com/&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/doves-constellations-for-the-lonely.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Constellations For The Lonely&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Doves&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://dovesofficial.com/&quot;&gt;
			Constellations For The Lonely
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;abbr&gt;EMI&lt;/abbr&gt; North&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://dustinohalloran.bandcamp.com/album/the-chromatic-sessions-2&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/dustin-ohalloran-chromatic-sessions.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of The Chromatic Sessions&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Dustin O’Halloran&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://dustinohalloran.bandcamp.com/album/the-chromatic-sessions-2&quot;&gt;
			The Chromatic Sessions
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Splinter&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://erlandcooper.bandcamp.com/album/asleep-on-the-wing&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/erland-cooper-asleep-on-the-wing.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Asleep On The Wing&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Erland Cooper&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://erlandcooper.bandcamp.com/album/asleep-on-the-wing&quot;&gt;
			Asleep On The Wing
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Mercury &lt;abbr&gt;KX&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.daughtersofcain.com/&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/ethel-cain-willoughby-tucker.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Willoughby Tucker, I&amp;#39;ll Always Love You&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Ethel Cain&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.daughtersofcain.com/&quot;&gt;
			Willoughby Tucker, I&#39;ll Always Love You
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Daughters of Cain&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://flockofdimes.bandcamp.com/album/the-life-you-save&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/flock-of-dimes-the-life-you-save.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of The Life You Save&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Flock of Dimes&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://flockofdimes.bandcamp.com/album/the-life-you-save&quot;&gt;
			The Life You Save
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.fredagain.com/&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/fred-again-usb.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of &amp;lt;abbr&amp;gt;USB&amp;lt;/abbr&amp;gt;&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Fred again..&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.fredagain.com/&quot;&gt;
			&lt;abbr&gt;USB&lt;/abbr&gt;
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Warner&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/handling&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/f-s-blumm-nils-frahm-handling.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Handling&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;F.S.Blumm, Nils Frahm&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://nilsfrahm.bandcamp.com/album/handling&quot;&gt;
			Handling
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Leiter&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://jennyhval.bandcamp.com/album/iris-silver-mist&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/jenny-hval-iris-silver-mist.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Iris Silver Mist&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Jenny Hval&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://jennyhval.bandcamp.com/album/iris-silver-mist&quot;&gt;
			Iris Silver Mist
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;abbr&gt;4AD&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://kangdingray.bandcamp.com/album/superfluid&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/kangding-ray-superfluid.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Superfluid&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Kangding Ray&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://kangdingray.bandcamp.com/album/superfluid&quot;&gt;
			Superfluid
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;ara&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://theliminanas.bandcamp.com/album/faded&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/the-liminanas-faded.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Faded&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;The Limiñanas&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://theliminanas.bandcamp.com/album/faded&quot;&gt;
			Faded
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Because Music&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://mattberninger.bandcamp.com/album/get-sunk&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/matt-berninger-get-sunk.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Get Sunk&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Matt Berninger&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://mattberninger.bandcamp.com/album/get-sunk&quot;&gt;
			Get Sunk
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Book Records&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://maxcooper.bandcamp.com/album/on-being&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/max-cooper-on-being.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of On Being&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Max Cooper&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://maxcooper.bandcamp.com/album/on-being&quot;&gt;
			On Being
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Mesh&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://mogwai.bandcamp.com/album/the-bad-fire&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/mogwai-the-bad-fire.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of The Bad Fire&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Mogwai&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://mogwai.bandcamp.com/album/the-bad-fire&quot;&gt;
			The Bad Fire
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Rock Action / Temporary Residence&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/night&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/nils-frahm-night.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Night&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Nils Frahm&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://nilsfrahm.bandcamp.com/album/night&quot;&gt;
			Night
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Leiter&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://rivalconsoles.bandcamp.com/album/landscape-from-memory&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/rival-consoles-landscape-from-memory.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Landscape from Memory&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Rival Consoles&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://rivalconsoles.bandcamp.com/album/landscape-from-memory&quot;&gt;
			Landscape from Memory
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Erased Tapes&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/without-wind-without-air-roger-eno-14133&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/roger-eno-without-wind-without-air.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Without Wind, Without Air&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Roger Eno&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/without-wind-without-air-roger-eno-14133&quot;&gt;
			Without Wind, Without Air
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Deutsche Grammophon&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://rone-music.bandcamp.com/album/le-mohican-original-motion-picture-soundtrack&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/rone-le-mohican.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Le Mohican &amp;lt;abbr&amp;gt;OST&amp;lt;/abbr&amp;gt;&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Rone&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://rone-music.bandcamp.com/album/le-mohican-original-motion-picture-soundtrack&quot;&gt;
			Le Mohican &lt;abbr&gt;OST&lt;/abbr&gt;
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;InFiné&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://sharonvanetten.bandcamp.com/album/sharon-van-etten-the-attachment-theory&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/sharon-van-etten-sharon-van-etten-and-the-attachment-theory.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Sharon Van Etten &amp;amp; The Attachment Theory&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Sharon Van Etten&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://sharonvanetten.bandcamp.com/album/sharon-van-etten-the-attachment-theory&quot;&gt;
			Sharon Van Etten &amp; The Attachment Theory
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Jagjaguwar&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://sofiakourtesis.bandcamp.com/album/volver&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/sofia-kourtesis-volver.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Volver&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Sofia Kourtesis&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://sofiakourtesis.bandcamp.com/album/volver&quot;&gt;
			Volver
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Ninja Tune&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://vanessa-wagner.bandcamp.com/album/philip-glass-the-complete-piano-etudes&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/vanessa-wagner-philip-glass-the-complete-piano-etudes.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Philip Glass: The Complete Piano Etudes&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Vanessa Wagner&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://vanessa-wagner.bandcamp.com/album/philip-glass-the-complete-piano-etudes&quot;&gt;
			Philip Glass: The Complete Piano Etudes
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;InFiné&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.yoshikacolwell.com/release/505627-yoshika-colwell-on-the-wing&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2025/yoshika-colwell-on-the-wing.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of On The Wing&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Yoshika Colwell&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.yoshikacolwell.com/release/505627-yoshika-colwell-on-the-wing&quot;&gt;
			On The Wing
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;abbr&gt;PIAS&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;I’ve also enjoyed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Abul Mogard, Rafael Anton Irisarri — &lt;a href=&quot;https://abulmogard.bandcamp.com/album/live-at-le-guess-who&quot;&gt;Live at Le Guess Who?&lt;/a&gt; [Black Knoll Editions]&lt;/li&gt;
&lt;li&gt;Abul Mogard — &lt;a href=&quot;https://abulmogard.bandcamp.com/album/quiet-pieces&quot;&gt;Quiet Pieces&lt;/a&gt; [Soft Echoes]&lt;/li&gt;
&lt;li&gt;Adrian Crowley — &lt;a href=&quot;https://adriancrowley.bandcamp.com/album/measure-of-joy-3&quot;&gt;Measure Of Joy&lt;/a&gt; [Valley of Eyes]&lt;/li&gt;
&lt;li&gt;Alexandre Tharaud — &lt;a href=&quot;https://www.warnerclassics.com/release/satie-discoveries&quot;&gt;Satie: Discoveries&lt;/a&gt; [Warner Classics]&lt;/li&gt;
&lt;li&gt;All Seeing Dolls (Anton Newcombe, Dot Allison) — &lt;a href=&quot;https://www.discogs.com/release/33181089-All-Seeing-Dolls-Parallel&quot;&gt;Parallel&lt;/a&gt; [A Records]&lt;/li&gt;
&lt;li&gt;Anika — &lt;a href=&quot;https://anika.bandcamp.com/album/abyss&quot;&gt;Abyss&lt;/a&gt; [Sacred Bones]&lt;/li&gt;
&lt;li&gt;Anouar Brahem — &lt;a href=&quot;https://ecmrecords.com/product/after-the-last-sky-anouar-brahem-anja-lechner-django-bates-dave-holland/&quot;&gt;After the Last Sky&lt;/a&gt; [&lt;abbr&gt;ECM&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Babx — &lt;a href=&quot;https://babxofficiel.com/album/amour-colosse/&quot;&gt;Amour Colosse&lt;/a&gt; [La Familia]&lt;/li&gt;
&lt;li&gt;Bacao Rhythm &amp;amp; Steel Band — &lt;a href=&quot;https://bacaorhythmandsteelband.bandcamp.com/album/big-crown-vaults-vol-4-bacao-rhythm-steel-band&quot;&gt;Big Crown Vaults Vol. 4&lt;/a&gt; [Big Crown]&lt;/li&gt;
&lt;li&gt;Balmorhea — &lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/the-trap-ost-balmorhea-14148&quot;&gt;The Trap &lt;abbr&gt;OST&lt;/abbr&gt;&lt;/a&gt; [Deutsche Grammophon]&lt;/li&gt;
&lt;li&gt;BJ Nilsen — &lt;a href=&quot;https://bennynilsen.bandcamp.com/album/true-than-nature&quot;&gt;True than Nature&lt;/a&gt; [Ideologic Organ]&lt;/li&gt;
&lt;li&gt;Bicep — &lt;a href=&quot;https://bicep.bandcamp.com/album/chroma-000&quot;&gt;Chroma 000&lt;/a&gt; / &lt;a href=&quot;https://bicep.bandcamp.com/album/takkuuk-original-soundtrack&quot;&gt;Takkuuk &lt;abbr&gt;OST&lt;/abbr&gt;&lt;/a&gt; [Ninja Tune]&lt;/li&gt;
&lt;li&gt;Biosphere — &lt;a href=&quot;https://biosphere.bandcamp.com/album/the-way-of-time&quot;&gt;The Way of Time&lt;/a&gt; [&lt;abbr&gt;AD 93&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Blanck Mass — &lt;a href=&quot;https://blanckmass.bandcamp.com/album/she-rides-shotgun&quot;&gt;She Rides Shotgun &lt;abbr&gt;OST&lt;/abbr&gt;&lt;/a&gt; [Invada]&lt;/li&gt;
&lt;li&gt;Blawan — &lt;a href=&quot;https://blawan.bandcamp.com/album/sickelixir&quot;&gt;SickElixir&lt;/a&gt; [&lt;abbr&gt;XL&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Blonde Redhead — &lt;a href=&quot;https://blonderedhead.bandcamp.com/album/the-shadow-of-the-guest&quot;&gt;The Shadow Of The Guest&lt;/a&gt; [section1]&lt;/li&gt;
&lt;li&gt;Brian Eno, Beatie Wolfe — &lt;a href=&quot;https://beatiewolfe.com/brian-eno&quot;&gt;Lateral / Liminal / Luminal&lt;/a&gt; [Verve]&lt;/li&gt;
&lt;li&gt;Caroline — &lt;a href=&quot;https://caroline.bandcamp.com/album/caroline-2&quot;&gt;Caroline 2&lt;/a&gt; [Rough Trade]&lt;/li&gt;
&lt;li&gt;Cherrystones, Demdike Stare — Who Owns The Dark? [&lt;abbr&gt;DDS&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Christopher Willits — &lt;a href=&quot;https://christopherwillits.bandcamp.com/album/new-moon&quot;&gt;New Moon&lt;/a&gt; [Ghostly]&lt;/li&gt;
&lt;li&gt;Clipping. — &lt;a href=&quot;https://clppng.bandcamp.com/album/dead-channel-sky-plus&quot;&gt;Dead Channel Sky Plus&lt;/a&gt; [Sub Pop]&lt;/li&gt;
&lt;li&gt;Darkside — &lt;a href=&quot;https://darkside.bandcamp.com/album/nothing&quot;&gt;Nothing&lt;/a&gt; [Matador]&lt;/li&gt;
&lt;li&gt;Demdike Stare, Kristen Pilon — &lt;a href=&quot;https://demdikestare.bandcamp.com/album/to-cut-shoot&quot;&gt;To Cut and Shoot&lt;/a&gt; [&lt;abbr&gt;DDS&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Destroyer — &lt;a href=&quot;https://destroyer.bandcamp.com/album/dans-boogie&quot;&gt;Dan’s Boogie&lt;/a&gt; [Merge]&lt;/li&gt;
&lt;li&gt;DJ Koze — &lt;a href=&quot;https://djkoze.bandcamp.com/album/music-can-hear-us-pampalp016d&quot;&gt;Music Can Hear Us&lt;/a&gt; [Pampa]&lt;/li&gt;
&lt;li&gt;DJ Python — &lt;a href=&quot;https://djpythonnyc.bandcamp.com/album/i-was-put-on-this-earth&quot;&gt;I was put on this earth&lt;/a&gt; [&lt;abbr&gt;XL&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Djrum — &lt;a href=&quot;https://djrum.bandcamp.com/album/under-tangled-silence&quot;&gt;Under Tangled Silence&lt;/a&gt; [Houndstooth]&lt;/li&gt;
&lt;li&gt;Donato Dozzy, Sabla — &lt;a href=&quot;https://sabla.bandcamp.com/album/morpho&quot;&gt;Morpho&lt;/a&gt; [Gang of Ducks]&lt;/li&gt;
&lt;li&gt;Dustin O’Halloran — Eleanor The Great &lt;abbr&gt;OST&lt;/abbr&gt; [Milan]&lt;/li&gt;
&lt;li&gt;Echolalia — &lt;a href=&quot;https://echolaliaband.bandcamp.com/album/echolalia&quot;&gt;Echolalia&lt;/a&gt; [Full Time Hobby]&lt;/li&gt;
&lt;li&gt;El Michels Affair — &lt;a href=&quot;https://elmichelsaffair.bandcamp.com/album/24-hr-sports&quot;&gt;24 Hr Sports&lt;/a&gt; [Big Crown]&lt;/li&gt;
&lt;li&gt;Emma Ruth Rundle — &lt;a href=&quot;https://emmaruthrundle.bandcamp.com/album/music-from-the-bella-vista&quot;&gt;Music from the Bella Vista&lt;/a&gt; [s/r]&lt;/li&gt;
&lt;li&gt;Erland Cooper — Berriedale [Mercury Classics]&lt;/li&gt;
&lt;li&gt;Ethel Cain — &lt;a href=&quot;https://daughtersofcain.com/&quot;&gt;Perverts&lt;/a&gt; [Daughters of Cain]&lt;/li&gt;
&lt;li&gt;Fabrizio Paterlini — &lt;a href=&quot;https://fabriziopaterlini.bandcamp.com/album/layers&quot;&gt;Layers&lt;/a&gt; [Believe]&lt;/li&gt;
&lt;li&gt;Greet Death — &lt;a href=&quot;https://greetdeath.bandcamp.com/album/die-in-love&quot;&gt;Die In Love&lt;/a&gt; [Deathwish]&lt;/li&gt;
&lt;li&gt;HAAi — &lt;a href=&quot;https://haai.bandcamp.com/album/humanise&quot;&gt;Humanise&lt;/a&gt; [Mute]&lt;/li&gt;
&lt;li&gt;Hilary Woods — &lt;a href=&quot;https://hilarywoodsmusic.bandcamp.com/album/night-cri&quot;&gt;Night Criú&lt;/a&gt; [Sacred Bones]&lt;/li&gt;
&lt;li&gt;Hildur Guðnadóttir — &lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/where-to-from-hildur-gunadottir-14090&quot;&gt;Where to From&lt;/a&gt; [Deutsche Grammophon]&lt;/li&gt;
&lt;li&gt;James Holden, Wacław Zimpel — &lt;a href=&quot;https://jamesholden.bandcamp.com/album/the-universe-will-take-care-of-you&quot;&gt;The Universe Will Take Care Of You&lt;/a&gt; [Border Community]&lt;/li&gt;
&lt;li&gt;Jay-Jay Johanson — Backstage [29 Music]&lt;/li&gt;
&lt;li&gt;Jefre Cantu–Ledesma — &lt;a href=&quot;https://jefrecantu-ledesma.bandcamp.com/album/gift-songs&quot;&gt;Gift Songs&lt;/a&gt; [Mexican Summer]&lt;/li&gt;
&lt;li&gt;John Maus — &lt;a href=&quot;https://johnmaus.bandcamp.com/album/later-than-you-think&quot;&gt;Later Than You Think&lt;/a&gt; [Young]&lt;/li&gt;
&lt;li&gt;Kali Malone, Drew McDowall — &lt;a href=&quot;https://kalimalone.bandcamp.com/album/magnetism&quot;&gt;Magnetism&lt;/a&gt; [Ideologic Organ]&lt;/li&gt;
&lt;li&gt;Kara–Lis Coverdale — &lt;a href=&quot;https://kara-liscoverdale.bandcamp.com/album/changes-in-air&quot;&gt;Changes in Air&lt;/a&gt; [Smalltown Supersound]&lt;/li&gt;
&lt;li&gt;Keaton Henson — &lt;a href=&quot;https://keatonhenson.bandcamp.com/album/parader&quot;&gt;Parader&lt;/a&gt; [&lt;abbr&gt;PIAS&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Keeley Forsyth, Matthew Bourne — &lt;a href=&quot;https://keeleyforsyth.bandcamp.com/album/hand-to-mouth&quot;&gt;Hand To Mouth&lt;/a&gt; [s/r]&lt;/li&gt;
&lt;li&gt;Lawrence English — &lt;a href=&quot;https://lawrenceenglish.bandcamp.com/album/even-the-horizon-knows-its-bounds&quot;&gt;Even The Horizon Knows Its Bounds&lt;/a&gt; [Room40]&lt;/li&gt;
&lt;li&gt;Lido Pimienta — &lt;a href=&quot;https://lidopimienta.bandcamp.com/album/la-belleza&quot;&gt;La Belleza&lt;/a&gt; [Anti]&lt;/li&gt;
&lt;li&gt;Loscil — &lt;a href=&quot;https://loscil.bandcamp.com/album/lake-fire&quot;&gt;Lake Fire&lt;/a&gt; [Kranky]&lt;/li&gt;
&lt;li&gt;Maria Somerville — &lt;a href=&quot;https://mariasomerville.bandcamp.com/album/luster&quot;&gt;Luster&lt;/a&gt; [&lt;abbr&gt;4AD&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Marissa Nadler — &lt;a href=&quot;https://marissanadler.bandcamp.com/album/new-radiations&quot;&gt;New Radiations&lt;/a&gt; [Sacred Bones / Bella Union]&lt;/li&gt;
&lt;li&gt;Max Richter — &lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/hamnet-ost-max-richter-14283&quot;&gt;Hamnet &lt;abbr&gt;OST&lt;/abbr&gt;&lt;/a&gt; [Deutsche Grammophon]&lt;/li&gt;
&lt;li&gt;Meitei / 冥丁 — &lt;a href=&quot;https://meitei.bandcamp.com/album/senny&quot;&gt;Sen&#39;nyū / 泉涌&lt;/a&gt; [Kitchen Label]&lt;/li&gt;
&lt;li&gt;Melaine Dalibert, David Sylvian — &lt;a href=&quot;https://mindtravels.bandcamp.com/album/vermilion-hours&quot;&gt;Vermilion Hours&lt;/a&gt; [Ici d’ailleurs]&lt;/li&gt;
&lt;li&gt;Melody’s Echo Chamber — &lt;a href=&quot;https://melodysechochamber.bandcamp.com/album/unclouded&quot;&gt;Unclouded&lt;/a&gt; [Domino]&lt;/li&gt;
&lt;li&gt;Milkweed — &lt;a href=&quot;https://milkweedfolk.bandcamp.com/album/remsc-la-2&quot;&gt;Remscéla&lt;/a&gt; [Broadside Hacks]&lt;/li&gt;
&lt;li&gt;Murcof — &lt;a href=&quot;https://murcofmusic.bandcamp.com/album/twin-color-extended-play-no-2&quot;&gt;Twin Color (Extended Play No. 2)&lt;/a&gt; [InFiné]&lt;/li&gt;
&lt;li&gt;Múm — &lt;a href=&quot;https://mumband.bandcamp.com/album/history-of-silence&quot;&gt;History of Silence&lt;/a&gt; [Morr Music]&lt;/li&gt;
&lt;li&gt;Nilüfer Yanya — &lt;a href=&quot;https://niluferyanya.bandcamp.com/album/dancing-shoes&quot;&gt;Dancing Shoes&lt;/a&gt; [Ninja Tunes]&lt;/li&gt;
&lt;li&gt;Olof Dreijer — &lt;a href=&quot;https://olofdreijer.bandcamp.com/album/iris&quot;&gt;Iris&lt;/a&gt; [Rabid]&lt;/li&gt;
&lt;li&gt;Oren Ambarchi, Eric Thielemans — &lt;a href=&quot;https://orenambarchi.bandcamp.com/album/kind-regards&quot;&gt;Kind Regards&lt;/a&gt; [&lt;abbr&gt;AD 93&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Oren Ambarchi, Fredrik Rasten — &lt;a href=&quot;https://orenambarchi.bandcamp.com/album/dragons-return&quot;&gt;Dragon’s Return&lt;/a&gt; [Viernullvier]&lt;/li&gt;
&lt;li&gt;Oren Ambarchi, Johan Berthling, Andreas Werliin — &lt;a href=&quot;https://orenambarchi.bandcamp.com/album/ghosted-iii&quot;&gt;Ghosted III&lt;/a&gt; [Drag City]&lt;/li&gt;
&lt;li&gt;Pelican — &lt;a href=&quot;https://pelican.bandcamp.com/album/flickering-resonance&quot;&gt;Flickering Resonance&lt;/a&gt; [Run For Cover]&lt;/li&gt;
&lt;li&gt;Perfume Genius — &lt;a href=&quot;https://perfumegenius.bandcamp.com/album/glory&quot;&gt;Glory&lt;/a&gt; [Matador]&lt;/li&gt;
&lt;li&gt;Peter Chilvers — &lt;a href=&quot;https://curiousmusicia.bandcamp.com/album/dust-4&quot;&gt;Dust 4&lt;/a&gt; [Curious Music]&lt;/li&gt;
&lt;li&gt;Rival Consoles — &lt;a href=&quot;https://rivalconsoles.bandcamp.com/album/mindseye&quot;&gt;MindsEye &lt;abbr&gt;OST&lt;/abbr&gt;&lt;/a&gt; [Erased Tapes]&lt;/li&gt;
&lt;li&gt;Rosalía — &lt;a href=&quot;https://www.rosalia.com/&quot;&gt;Lux&lt;/a&gt; [Columbia]&lt;/li&gt;
&lt;li&gt;S. Carey — &lt;a href=&quot;https://scarey.bandcamp.com/album/watercress&quot;&gt;Watercress&lt;/a&gt; [Jagjaguwar]&lt;/li&gt;
&lt;li&gt;Sandwell District — &lt;a href=&quot;https://sandwelldistrict.bandcamp.com/album/end-beginnings&quot;&gt;End Beginnings&lt;/a&gt; [Point of Departure]&lt;/li&gt;
&lt;li&gt;Sarah Davachi — &lt;a href=&quot;https://sarahdavachi.bandcamp.com/album/basse-brevis&quot;&gt;Basse Brevis&lt;/a&gt; [Potraits &lt;abbr&gt;GRM&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Stephen O’Malley — &lt;a href=&quot;https://stephenomalley.bandcamp.com/album/but-remember-what-you-have-had&quot;&gt;But remember what you have had&lt;/a&gt; [Potraits &lt;abbr&gt;GRM&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Stereolab — &lt;a href=&quot;https://stereolab.bandcamp.com/album/instant-holograms-on-metal-film&quot;&gt;Instant Holograms On Metal Film&lt;/a&gt; [Duophonic UHF Disks / Warp]&lt;/li&gt;
&lt;li&gt;Steve Gunn — &lt;a href=&quot;https://stevegunn.bandcamp.com/album/daylight-daylight&quot;&gt;Daylight Daylight&lt;/a&gt; [No Quarter]&lt;/li&gt;
&lt;li&gt;Sunn O))) — &lt;a href=&quot;https://sunn.bandcamp.com/album/eternitys-pillars-b-w-raise-the-chalice-reverential&quot;&gt;Eternity’s Pillars b/w Raise the Chalice &amp;amp; Reverential&lt;/a&gt; [Sub Pop]&lt;/li&gt;
&lt;li&gt;Sławomir Zubrzycki — &lt;a href=&quot;https://slawomirzubrzycki.bandcamp.com/album/viola-organista-monologues-dialogues&quot;&gt;Viola Organista: Monologues &amp;amp; Dialogues&lt;/a&gt; [Leiter]&lt;/li&gt;
&lt;li&gt;The Antlers — &lt;a href=&quot;https://theantlers.bandcamp.com/album/blight-2&quot;&gt;Blight&lt;/a&gt; [Transgressive]&lt;/li&gt;
&lt;li&gt;The Black Dog — &lt;a href=&quot;https://theblackdog.bandcamp.com/album/my-brutal-life-ep&quot;&gt;My Brutal Life &lt;abbr&gt;EP&lt;/abbr&gt;&lt;/a&gt; / &lt;a href=&quot;https://theblackdog.bandcamp.com/album/my-brutal-life-2&quot;&gt;My Brutal Life 2&lt;/a&gt; [Dust Science]&lt;/li&gt;
&lt;li&gt;The Black Heart Procession — &lt;a href=&quot;https://theblackheartprocession.bandcamp.com/album/hearts-and-tanks&quot;&gt;Hearts and Tanks&lt;/a&gt; [Solid Brass]&lt;/li&gt;
&lt;li&gt;The Black Keys — No Rain, No Flowers [Easy Eye Sound, Warner]&lt;/li&gt;
&lt;li&gt;The Weather Station — &lt;a href=&quot;https://theweatherstation.bandcamp.com/album/humanhood&quot;&gt;Humanhood&lt;/a&gt; [Fat Possum]&lt;/li&gt;
&lt;li&gt;Thylacine — &lt;a href=&quot;https://www.thylacinemusic.com/&quot;&gt;Roads Vol. 3&lt;/a&gt; [Intuitive Records]&lt;/li&gt;
&lt;li&gt;Tortoise — &lt;a href=&quot;https://intlanthem.bandcamp.com/album/touch&quot;&gt;Touch&lt;/a&gt; [Intl Anthem]&lt;/li&gt;
&lt;li&gt;U.S. Girls — &lt;a href=&quot;https://usgirls.bandcamp.com/album/scratch-it&quot;&gt;Scratch It&lt;/a&gt; [&lt;abbr&gt;4AD&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Vanishing Twin — &lt;a href=&quot;https://vanishingtwinmusic.bandcamp.com/album/in-piscina&quot;&gt;In Piscina!&lt;/a&gt; [Fire Records]&lt;/li&gt;
&lt;li&gt;Verses &lt;abbr&gt;GT&lt;/abbr&gt; (Jacques Greene, Nosaj Thing) — &lt;a href=&quot;https://versesgt.bandcamp.com/album/verses-gt&quot;&gt;Verses &lt;abbr&gt;GT&lt;/abbr&gt;&lt;/a&gt; [Luckyme]&lt;/li&gt;
&lt;li&gt;William Basinski, Richard Chartier — &lt;a href=&quot;https://lineimprint.bandcamp.com/album/aurora-terminalis&quot;&gt;Aurora Terminalis&lt;/a&gt; [&lt;abbr&gt;LINE&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Yasmine Hamdan — &lt;a href=&quot;https://yasminehamdan.bandcamp.com/album/yasmine-hamdan-i-remember-i-forget&quot;&gt;I remember I forget بنسى وبتذكر&lt;/a&gt; [Crammed Discs, &lt;abbr&gt;PIAS&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Ólafur Arnalds, Talos — &lt;a href=&quot;https://www.adawning.net/&quot;&gt;A Dawning&lt;/a&gt; [&lt;abbr&gt;OPIA&lt;/abbr&gt;, Mercury &lt;abbr&gt;KX&lt;/abbr&gt;]&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;From previous years.&lt;/strong&gt; Played a lot of &lt;a href=&quot;https://shida-shahabi.bandcamp.com/album/living-circle&quot;&gt;Living Circle&lt;/a&gt; (Fat Cat, 2023) and &lt;a href=&quot;https://shida-shahabi.bandcamp.com/album/shifts&quot;&gt;Shifts&lt;/a&gt; (Fat Cat, 2019) from Shida Shahabi, whom I’d seen in 2023 opening for &lt;abbr&gt;AWVFTS&lt;/abbr&gt; at the Barbican but forgot to follow up on, only to go all &lt;em&gt;wow, what is this heavenly music?? and why does it feel so eerily familiar?&lt;/em&gt; when my friend Alex recommended it a few months ago. Also big on &lt;abbr&gt;MONO&lt;/abbr&gt;’s &lt;a href=&quot;https://monoofjapan.bandcamp.com/album/oath&quot;&gt;Oath&lt;/a&gt; (2024), which I discovered too late to include in last year’s list.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Live shows.&lt;/strong&gt; Sharon van Etten in Berlin; Matt Berninger in Glasgow; Fred again.. in Rome.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This has been the 12th edition of my favorite records. Timeline: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other 2025 lists: &lt;a href=&quot;https://boomkat.com/charts/boomkat-end-of-year-charts-2025&quot;&gt;Boomkat&lt;/a&gt;, &lt;a href=&quot;https://bleep.com/albums-of-the-year-2025&quot;&gt;Bleep&lt;/a&gt;, &lt;a href=&quot;https://www.roughtrade.com/en-de/collection/albums-of-the-year-2025&quot;&gt;Rough Trade&lt;/a&gt;, &lt;a href=&quot;https://thequietus.com/tq-charts/albums-of-the-year/best-albums-of-2025/&quot;&gt;The Quietus&lt;/a&gt;, &lt;a href=&quot;https://www.thewire.co.uk/issues/charts/2025-rewind-contributors-charts&quot;&gt;The Wire&lt;/a&gt;, &lt;a href=&quot;https://hicks.design/journal/hicks-design-2025-annual-report&quot;&gt;Jon Hicks&lt;/a&gt;, &lt;a href=&quot;https://colly.com/journal/twenty-twentyfive-in-music&quot;&gt;Simon Collison&lt;/a&gt;, &lt;a href=&quot;https://scottboms.com/documenting/twelve-from-25&quot;&gt;Scott Boms&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2024</title>
		<link href="https://danburzo.ro/favorite-records-2024/" />
		<updated>2024-12-22T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2024/</id>
		<content type="html"
			>&lt;p&gt;This year I’ve listened to music for almost 100,000 minutes, or about four hours daily. Here are 47 albums I’ve played a lot, listed in alphabetical order.&lt;/p&gt;
&lt;ul class=&quot;records&quot;&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://adriannelenker.bandcamp.com/album/bright-future&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/adrianne-lenker-bright-future.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Bright Future&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Adrianne Lenker&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://adriannelenker.bandcamp.com/album/bright-future&quot;&gt;
			Bright Future
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;abbr&gt;4AD&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://noton.info/product/n-064/&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/alva-noto-hybrid-iii.png&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of HYbr:ID III&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Alva Noto&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://noton.info/product/n-064/&quot;&gt;
			HYbr:ID III
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Noton&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://bacaorhythmandsteelband.bandcamp.com/album/brsb&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/bacao-rhythm-steel-band-brsb.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of &amp;lt;abbr&amp;gt;BRSB&amp;lt;/abbr&amp;gt;&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Bacao Rhythm &amp; Steel Band&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://bacaorhythmandsteelband.bandcamp.com/album/brsb&quot;&gt;
			&lt;abbr&gt;BRSB&lt;/abbr&gt;
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Big Crown&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://benlukasboysen.bandcamp.com/album/alta-ripa&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/ben-lukas-boysen-alta-ripa.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Alta Ripa&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Ben Lukas Boysen&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://benlukasboysen.bandcamp.com/album/alta-ripa&quot;&gt;
			Alta Ripa
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Erased Tapes&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://bethgibbons.dominomart.com/lives-outgrown-lp&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/beth-gibbons-lives-outgrown.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Lives Outgrown&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Beth Gibbons&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://bethgibbons.dominomart.com/lives-outgrown-lp&quot;&gt;
			Lives Outgrown
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Domino&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://boniver.bandcamp.com/album/sable&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/bon-iver-sable.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Sable,&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Bon Iver&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://boniver.bandcamp.com/album/sable&quot;&gt;
			Sable,
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Jagjaguwar&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://caribouband.bandcamp.com/album/honey-2&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/caribou-honey.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Honey&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Caribou&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://caribouband.bandcamp.com/album/honey-2&quot;&gt;
			Honey
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;City Slang&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://cocowithlove.bandcamp.com/album/2&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/coco-2.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of 2&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Coco&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://cocowithlove.bandcamp.com/album/2&quot;&gt;
			2
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Self released&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://danielherskedal.bandcamp.com/album/echoes-of-solitude&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/daniel-herskedal-echoes-of-solitude.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Echoes of Solitude&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Daniel Herskedal &amp; Mattia Vlad Morleo&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://danielherskedal.bandcamp.com/album/echoes-of-solitude&quot;&gt;
			Echoes of Solitude
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;E2&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://danielherskedal.bandcamp.com/album/a-single-sunbeam&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/daniel-herskedal-a-single-sunbeam.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of A Single Sunbeam&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Daniel Herskedal&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://danielherskedal.bandcamp.com/album/a-single-sunbeam&quot;&gt;
			A Single Sunbeam
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;E2&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://danielherskedal.bandcamp.com/album/call-for-winter-ii-resonance&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/daniel-herskedal-call-for-winter-ii.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Call For Winter II: Resonance&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Daniel Herskedal&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://danielherskedal.bandcamp.com/album/call-for-winter-ii-resonance&quot;&gt;
			Call For Winter II: Resonance
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Edition Records&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/1001-dustin-ohalloran-13291&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/dustin-o-halloran-1001.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of 1001&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Dustin O’Halloran&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/1001-dustin-ohalloran-13291&quot;&gt;
			1001
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Deutsche Grammophon&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://efterklang.bandcamp.com/album/things-we-have-in-common&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/efterklang-things-we-have-in-common.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Things We Have in Common&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Efterklang&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://efterklang.bandcamp.com/album/things-we-have-in-common&quot;&gt;
			Things We Have in Common
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;City Slang&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://shop.mercurykx.com/products/carve-the-runes-then-be-content-with-silence-exclusive-limited-edition-lp-1&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/erland-cooper.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Carve the Runes Then Be Content With Silence&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Erland Cooper&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://shop.mercurykx.com/products/carve-the-runes-then-be-content-with-silence-exclusive-limited-edition-lp-1&quot;&gt;
			Carve the Runes Then Be Content With Silence
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Mercury KX&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://fredagainagain.bandcamp.com/album/ten-days&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/fred-again-ten-days.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of ten days&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Fred again..&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://fredagainagain.bandcamp.com/album/ten-days&quot;&gt;
			ten days
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Atlantic&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://godspeedyoublackemperor.bandcamp.com/album/no-title-as-of-13-february-2024-28340-dead&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/godspeed-you-black-emperor.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of “NO TITLE AS OF 13 FEBRUARY 2024 28,340 DEAD”&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Godspeed You! Black Emperor&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://godspeedyoublackemperor.bandcamp.com/album/no-title-as-of-13-february-2024-28340-dead&quot;&gt;
			“NO TITLE AS OF 13 FEBRUARY 2024 28,340 DEAD”
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Constellation&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://hermanosgutierrez.bandcamp.com/album/sonido-c-smico&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/hermanos-gutierrez-sonido-cosmico.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Sonido Cósmico&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Hermanos Gutiérrez&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://hermanosgutierrez.bandcamp.com/album/sonido-c-smico&quot;&gt;
			Sonido Cósmico
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Easy Eye Sound&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://jamiexx.bandcamp.com/album/in-waves&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/jamie-xx-in-waves.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of In Waves&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Jamie xx&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://jamiexx.bandcamp.com/album/in-waves&quot;&gt;
			In Waves
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Young&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://jonhopkins.bandcamp.com/album/ritual&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/jon-hopkins-ritual.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Ritual&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Jon Hopkins&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://jonhopkins.bandcamp.com/album/ritual&quot;&gt;
			Ritual
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Domino&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://kalimalone.bandcamp.com/album/all-life-long&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/kali-malone-all-life-long.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of All Life Long&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Kali Malone&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://kalimalone.bandcamp.com/album/all-life-long&quot;&gt;
			All Life Long
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Ideologic Organ&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://shop.mercurykx.com/products/somnambulant-cycles-vinyl-lp&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/keaton-henson-somnambulant-cycles.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Somnambulant Cycles&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Keaton Henson&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://shop.mercurykx.com/products/somnambulant-cycles-vinyl-lp&quot;&gt;
			Somnambulant Cycles
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Mercury KX&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://kessoncoda.bandcamp.com/album/outerstate&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/kessoncoda-outerstate.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Outerstate&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Kessoncoda&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://kessoncoda.bandcamp.com/album/outerstate&quot;&gt;
			Outerstate
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Gondwana Records&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://khruangbin.bandcamp.com/album/a-la-sala&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/khruangbin-a-la-sala.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of A La Sala&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Khruangbin&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://khruangbin.bandcamp.com/album/a-la-sala&quot;&gt;
			A La Sala
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Dead Oceans&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://kiasmos.bandcamp.com/album/ii&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/kiasmos-ii.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of II&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Kiasmos&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://kiasmos.bandcamp.com/album/ii&quot;&gt;
			II
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Erased Tapes&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://laurelhalo.bandcamp.com/album/octavia&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/laurel-halo-octavia.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Octavia&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Laurel Halo&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://laurelhalo.bandcamp.com/album/octavia&quot;&gt;
			Octavia
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Portraits &lt;abbr&gt;GRM&lt;/abbr&gt; / Shelter Press&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://lomamusic.bandcamp.com/album/how-will-i-live-without-a-body&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/loma-how-will-i-live-without-a-body.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of How Will I Live Without a Body?&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Loma&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://lomamusic.bandcamp.com/album/how-will-i-live-without-a-body&quot;&gt;
			How Will I Live Without a Body?
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Sub Pop&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://loscil.bandcamp.com/album/chroma&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/loscil-lawrence-english-chroma.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Chroma&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Loscil &amp; Lawrence English&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://loscil.bandcamp.com/album/chroma&quot;&gt;
			Chroma
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Self-released&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://masayoshifujita.bandcamp.com/album/migratory&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/masayoshi-fujita-migratory.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Migratory&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Masayoshi Fujita&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://masayoshifujita.bandcamp.com/album/migratory&quot;&gt;
			Migratory
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Erased Tapes&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://maxrichter.lnk.to/InALandscapeID&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/max-richter-in-a-landscape.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of In A Landscape&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Max Richter&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://maxrichter.lnk.to/InALandscapeID&quot;&gt;
			In A Landscape
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Decca&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/mirror-music-michael-a-muller-13264&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/michael-a-muller-mirror-music.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Mirror Music&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Michael A. Muller&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/mirror-music-michael-a-muller-13264&quot;&gt;
			Mirror Music
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Deutsche Grammophon&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://cafemolly.bandcamp.com/album/on-the-lips&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/molly-lewis-on-the-lips.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of On The Lips&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Molly Lewis&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://cafemolly.bandcamp.com/album/on-the-lips&quot;&gt;
			On The Lips
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Jagjaguwar&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://nadasurf.bandcamp.com/album/moon-mirror&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/nada-surf-moon-mirror.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Moon Mirror&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Nada Surf&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://nadasurf.bandcamp.com/album/moon-mirror&quot;&gt;
			Moon Mirror
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;New West&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://nickcave-badseeds.ffm.to/wild-god&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/nick-cave-wild-god.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Wild God&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Nick Cave &amp; The Bad Seeds&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://nickcave-badseeds.ffm.to/wild-god&quot;&gt;
			Wild God
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;&lt;abbr&gt;PIAS&lt;/abbr&gt;&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/day&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/nils-frahm-day.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Day&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Nils Frahm&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://nilsfrahm.bandcamp.com/album/day&quot;&gt;
			Day
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Leiter&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://nonkeen.bandcamp.com/album/all-good&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/nonkeen-all-good.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of All good?&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Nonkeen&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://nonkeen.bandcamp.com/album/all-good&quot;&gt;
			All good?
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Leiter&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://ottoatotland.bandcamp.com/album/exin&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/otto-a-totland-exin.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Exin&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Otto A. Totland&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://ottoatotland.bandcamp.com/album/exin&quot;&gt;
			Exin
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Leiter&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://phosphorescent.lnk.to/revelator&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/phosphorescent-revelator.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Revelator&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Phosphorescent&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://phosphorescent.lnk.to/revelator&quot;&gt;
			Revelator
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Verve&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://dg.lnk.to/Skies-Rarities&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/roger-eno-skies-rarities.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of The Skies: Rarities&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Roger Eno&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://dg.lnk.to/Skies-Rarities&quot;&gt;
			The Skies: Rarities
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Deutsche Grammophon&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://theblackdog.bandcamp.com/album/other-like-me&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/the-black-dog-other-like-me.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Other, Like Me&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;The Black Dog&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://theblackdog.bandcamp.com/album/other-like-me&quot;&gt;
			Other, Like Me
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Dust Science&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://theblackkeys.bandcamp.com/album/ohio-players&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/the-black-keys-ohio-players.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Ohio Players&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;The Black Keys&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://theblackkeys.bandcamp.com/album/ohio-players&quot;&gt;
			Ohio Players
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Easy Eye Sound&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://thecure.lnk.to/SongsOfALostWorld&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/the-cure-songs-of-a-lost-world.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Songs Of A Lost World&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;The Cure&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://thecure.lnk.to/SongsOfALostWorld&quot;&gt;
			Songs Of A Lost World
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Polydor&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://thedecemberists.bandcamp.com/album/as-it-ever-was-so-it-will-be-again&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/the-decemberists-as-it-ever-was.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of As It Ever Was, So It Will Be Again&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;The Decemberists&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://thedecemberists.bandcamp.com/album/as-it-ever-was-so-it-will-be-again&quot;&gt;
			As It Ever Was, So It Will Be Again
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;YABB&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://thesmile.bandcamp.com/album/wall-of-eyes&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/the-smile-wall-of-eyes.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Wall of Eyes&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;The Smile&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://thesmile.bandcamp.com/album/wall-of-eyes&quot;&gt;
			Wall of Eyes
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;XL&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://tindersticks.bandcamp.com/album/soft-tissue&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/tindersticks-soft-tissue.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Soft Tissue&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Tindersticks&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://tindersticks.bandcamp.com/album/soft-tissue&quot;&gt;
			Soft Tissue
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;City Slang&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://night-school.bandcamp.com/album/tristwch-y-fenywod&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/tristwch-y-fenywod.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Tristwch Y Fenywod&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Tristwch Y Fenywod&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://night-school.bandcamp.com/album/tristwch-y-fenywod&quot;&gt;
			Tristwch Y Fenywod
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Night School&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://waxahatchee.bandcamp.com/album/tigers-blood&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/waxahatchee-tigers-blood.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Tigers Blood&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;Waxahatchee&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://waxahatchee.bandcamp.com/album/tigers-blood&quot;&gt;
			Tigers Blood
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;Anti–&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;record&quot;&gt;
	&lt;a href=&quot;https://whomadewho.bandcamp.com/album/kiss-forget&quot; class=&quot;record-cover&quot; tabindex=&quot;-1&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/favorite-records-2024/whomadewho-kiss-and-forget.jpg&quot; width=&quot;400&quot; height=&quot;400&quot; alt=&quot;Cover of Kiss &amp;amp; Forget&quot; loading=&quot;lazy&quot; /&gt;
	&lt;/a&gt;
	&lt;p class=&quot;record-info&quot;&gt;
		&lt;strong class=&quot;record-artist&quot;&gt;WhoMadeWho&lt;/strong&gt;
		&lt;a class=&quot;record-title&quot; href=&quot;https://whomadewho.bandcamp.com/album/kiss-forget&quot;&gt;
			Kiss &amp; Forget
		&lt;/a&gt;
		&lt;span class=&quot;record-label&quot;&gt;The Moment&lt;/span&gt;
	&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other albums I’ve enjoyed this year:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Adam Wiltzie — &lt;a href=&quot;https://adamwiltzie.bandcamp.com/album/eleven-fugues-for-sodium-pentothal&quot;&gt;Eleven Fugues For Sodium Pentothal&lt;/a&gt; [Kranky]&lt;/li&gt;
&lt;li&gt;Alessandra Novaga — &lt;a href=&quot;https://dieschachtelrecords.bandcamp.com/album/the-artistic-image-is-always-a-miracle&quot;&gt;The Artistic Image Is Always a Miracle&lt;/a&gt; [Die Schachtel]&lt;/li&gt;
&lt;li&gt;Alison Cotton — &lt;a href=&quot;https://alisoncotton-uk.bandcamp.com/album/engelchen&quot;&gt;Engelchen&lt;/a&gt; [Rocket Recordings]&lt;/li&gt;
&lt;li&gt;Alva Noto — &lt;a href=&quot;https://noton.info/product/n-062/&quot;&gt;Xerrox, Vol.5&lt;/a&gt; [Noton]&lt;/li&gt;
&lt;li&gt;Angel Olsen — &lt;a href=&quot;https://angelolsen.bandcamp.com/album/cosmic-waves-volume-1&quot;&gt;Cosmic Waves Volume 1&lt;/a&gt; [Jagjaguwaar]&lt;/li&gt;
&lt;li&gt;Antonina Nowacka — &lt;a href=&quot;https://antoninanowacka.bandcamp.com/album/sylphine-soporifera&quot;&gt;Sylphine Soporifera&lt;/a&gt; [Mondoj]&lt;/li&gt;
&lt;li&gt;Aphex Twin — &lt;a href=&quot;https://aphextwin.bandcamp.com/album/music-from-the-merch-desk-2016-2023&quot;&gt;Music From The Merch Desk (2016–2023)&lt;/a&gt; [Warp]&lt;/li&gt;
&lt;li&gt;Ben Lukas Boysen — &lt;a href=&quot;https://benlukasboysen.bandcamp.com/album/falling-into-place-original-score&quot;&gt;Falling into Place OST&lt;/a&gt; [Erased Tapes]&lt;/li&gt;
&lt;li&gt;Blanck Mass — &lt;a href=&quot;https://blanckmass.bandcamp.com/album/bloodhound-you-2&quot;&gt;Bloodhound / You&lt;/a&gt; [Weirding Way]&lt;/li&gt;
&lt;li&gt;Bobby Oroza — &lt;a href=&quot;https://bobbyoroza.bandcamp.com/album/queen-of-the-barrio-b-w-goddess&quot;&gt;Goddess / Queen of the Barrio&lt;/a&gt; [Big Crown]&lt;/li&gt;
&lt;li&gt;Bolis Pupul — &lt;a href=&quot;https://bolispupul.bandcamp.com/album/letter-to-yu&quot;&gt;Letter To Yu&lt;/a&gt; [Deewee]&lt;/li&gt;
&lt;li&gt;Bruce Brubaker — &lt;a href=&quot;https://bruce-brubaker.bandcamp.com/album/eno-piano-2&quot;&gt;Eno Piano 2&lt;/a&gt; [InFiné]&lt;/li&gt;
&lt;li&gt;Burial &amp;amp; Kode9 — &lt;a href=&quot;https://burial.bandcamp.com/album/phoneglow-eyes-go-blank&quot;&gt;Phoneglow / Eyes Go Blank&lt;/a&gt; [Hyperdub]&lt;/li&gt;
&lt;li&gt;Burial — &lt;a href=&quot;https://burial.bandcamp.com/album/dreamfear-boy-sent-from-above&quot;&gt;Dreamfear / Boy Sent From Above&lt;/a&gt; [XL]&lt;/li&gt;
&lt;li&gt;Chastity Belt — &lt;a href=&quot;https://chastity-belt.bandcamp.com/album/live-laugh-love&quot;&gt;Live Laugh Love&lt;/a&gt; [Suicide Squeeze]&lt;/li&gt;
&lt;li&gt;Christian Löffler — &lt;a href=&quot;https://christianloffler.bandcamp.com/album/a-life&quot;&gt;A Life&lt;/a&gt; [Ki Records]&lt;/li&gt;
&lt;li&gt;Chuck Johnson — &lt;a href=&quot;https://chuckjohnson.bandcamp.com/album/sun-glories&quot;&gt;Sun Glories&lt;/a&gt; [Western Vinyl]&lt;/li&gt;
&lt;li&gt;Cigarettes After Sex — &lt;a href=&quot;https://cigarettesaftersex.bandcamp.com/album/xs&quot;&gt;X’s&lt;/a&gt; [Partisan]&lt;/li&gt;
&lt;li&gt;Cowboy Sadness — &lt;a href=&quot;https://cowboysadness.bandcamp.com/album/selected-jambient-works-vol-1&quot;&gt;Selected Jambient Works vol. I&lt;/a&gt; [People Teeth]&lt;/li&gt;
&lt;li&gt;Craven Faults — &lt;a href=&quot;https://cravenfaults.bandcamp.com/album/bounds&quot;&gt;Bounds&lt;/a&gt; [The Leaf Label]&lt;/li&gt;
&lt;li&gt;Donato Dozzy — &lt;a href=&quot;https://donatodozzy.bandcamp.com/album/magda&quot;&gt;Magda&lt;/a&gt; [Spazio Disponibile]&lt;/li&gt;
&lt;li&gt;Four Tet — &lt;a href=&quot;https://fourtet.bandcamp.com/album/three&quot;&gt;Three&lt;/a&gt; [Text]&lt;/li&gt;
&lt;li&gt;Ganavya — &lt;a href=&quot;https://ganavya.bandcamp.com/album/draw-something-beautiful&quot;&gt;Draw Something Beautiful&lt;/a&gt; [Leiter]&lt;/li&gt;
&lt;li&gt;Hamish Hawk — &lt;a href=&quot;https://hamishhawk.tmstor.es/&quot;&gt;A Firmer Hand&lt;/a&gt; [So Recordings]&lt;/li&gt;
&lt;li&gt;Helado Negro — &lt;a href=&quot;https://heladonegro.bandcamp.com/album/phasor&quot;&gt;Phasor&lt;/a&gt; [&lt;span class=&quot;sc&quot;&gt;4AD&lt;/span&gt;]&lt;/li&gt;
&lt;li&gt;Idles — &lt;a href=&quot;https://idlesband.bandcamp.com/album/tangk&quot;&gt;Tangk&lt;/a&gt; [Partisan]&lt;/li&gt;
&lt;li&gt;Joep Beving &amp;amp; Maarten Vos — &lt;a href=&quot;https://maartenvos.bandcamp.com/album/vision-of-contentment&quot;&gt;Vision of Contentment&lt;/a&gt; [Leiter]&lt;/li&gt;
&lt;li&gt;Keeley Forsyth — &lt;a href=&quot;https://keeleyforsyth.bandcamp.com/album/the-hollow&quot;&gt;The Hollow&lt;/a&gt; [Fat Cat]&lt;/li&gt;
&lt;li&gt;Kelly Lee Owens — &lt;a href=&quot;https://kellyleeowens.bandcamp.com/album/dreamstate&quot;&gt;Dreamstate&lt;/a&gt; [dh2]&lt;/li&gt;
&lt;li&gt;King Hannah — &lt;a href=&quot;https://kinghannah.bandcamp.com/album/big-swimmer&quot;&gt;Big Swimmer&lt;/a&gt; [City Slang]&lt;/li&gt;
&lt;li&gt;Lionel Limiñana, David Menke — Heureux Gagnants OST [Berreto Music]&lt;/li&gt;
&lt;li&gt;Loscil — &lt;a href=&quot;https://loscil.bandcamp.com/album/umbel&quot;&gt;Umbel&lt;/a&gt; [Self-released]&lt;/li&gt;
&lt;li&gt;Marika Hackman — &lt;a href=&quot;https://marikahackman.bandcamp.com/album/big-sigh-3&quot;&gt;Big Sigh&lt;/a&gt; [Chrysalis]&lt;/li&gt;
&lt;li&gt;Max Cooper — &lt;a href=&quot;https://maxcooper.bandcamp.com/album/seme&quot;&gt;Seme&lt;/a&gt; [Mesh]&lt;/li&gt;
&lt;li&gt;Max Richter — &lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/richter-sleep-piano-edition-13346&quot;&gt;Sleep (Piano Edition)&lt;/a&gt; [Deutsche Grammophon]&lt;/li&gt;
&lt;li&gt;Michelle Gurevich — &lt;a href=&quot;https://michellegurevich.bandcamp.com/album/it-was-the-moment&quot;&gt;It Was the Moment&lt;/a&gt; [Self-released]&lt;/li&gt;
&lt;li&gt;Milkweed — &lt;a href=&quot;https://milkweedfolk.bandcamp.com/album/folklore-1979-2&quot;&gt;Folklore 1979&lt;/a&gt; [Broadside Hacks]&lt;/li&gt;
&lt;li&gt;Murcof — &lt;a href=&quot;https://murcofmusic.bandcamp.com/album/twin-color-vol-i&quot;&gt;Twin Color, Vol. 1&lt;/a&gt; [InFiné]&lt;/li&gt;
&lt;li&gt;Nils Frahm — &lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/paris&quot;&gt;Paris&lt;/a&gt; [Leiter]&lt;/li&gt;
&lt;li&gt;Oren Ambarchi, Johan Berthling &amp;amp; Andreas Werliin — &lt;a href=&quot;https://orenambarchi.bandcamp.com/album/ghosted-ii&quot;&gt;Ghosted II&lt;/a&gt; [Drag City]&lt;/li&gt;
&lt;li&gt;Pelican — &lt;a href=&quot;https://pelican.bandcamp.com/album/adrift-tending-the-embers&quot;&gt;Adrift / Tending the Embers&lt;/a&gt; [Pelicansong]&lt;/li&gt;
&lt;li&gt;Philip Glass — &lt;a href=&quot;https://philipglass.bandcamp.com/album/philip-glass-solo&quot;&gt;Philip Glass Solo&lt;/a&gt; [Orange Mountain Music]&lt;/li&gt;
&lt;li&gt;Phosphorescent — &lt;a href=&quot;https://phosphorescent.lnk.to/OhCanada&quot;&gt;Oh, Canada OST&lt;/a&gt; [Verve]&lt;/li&gt;
&lt;li&gt;Ryuichi Sakamoto — &lt;a href=&quot;https://opus.film/&quot;&gt;Opus&lt;/a&gt; [Milan Records]&lt;/li&gt;
&lt;li&gt;Saint Etienne — &lt;a href=&quot;https://heavenlyrecordings.bandcamp.com/album/saint-etienne-the-night&quot;&gt;The Night&lt;/a&gt; [&lt;abbr&gt;PIAS&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Sarah Davachi — &lt;a href=&quot;https://sarahdavachi.bandcamp.com/album/the-head-as-form-d-in-the-crier-s-choir&quot;&gt;The Head as Form’d in the Crier’s Choir&lt;/a&gt; [Late Music]&lt;/li&gt;
&lt;li&gt;The Black Dog — &lt;a href=&quot;https://theblackdog.bandcamp.com/album/isolation-ep&quot;&gt;Isolation EP&lt;/a&gt; / &lt;a href=&quot;https://theblackdog.bandcamp.com/album/seclusion-ep&quot;&gt;Seclusion EP&lt;/a&gt; [Dust Science]&lt;/li&gt;
&lt;li&gt;The National — &lt;a href=&quot;https://thenational.bandcamp.com/album/rome&quot;&gt;Rome&lt;/a&gt; [&lt;abbr&gt;4AD&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;The Smile — &lt;a href=&quot;https://thesmile.x-l.co/cutouts&quot;&gt;Cutouts&lt;/a&gt; [&lt;abbr&gt;XL&lt;/abbr&gt;]&lt;/li&gt;
&lt;li&gt;Timber Timbre — Interview [Hot Dreams]&lt;/li&gt;
&lt;li&gt;Tindersticks — Mayday ’22 [Lucky Dog]&lt;/li&gt;
&lt;li&gt;Tycho — &lt;a href=&quot;https://tycho.bandcamp.com/album/infinite-health&quot;&gt;Infinite Health&lt;/a&gt; [Ninja Tune]&lt;/li&gt;
&lt;li&gt;Valerinne — &lt;a href=&quot;https://valerinne.bandcamp.com/album/ver-sacrum&quot;&gt;Ver Sacrum&lt;/a&gt; [Self-released]&lt;/li&gt;
&lt;li&gt;Woods — &lt;a href=&quot;https://woodsfamilyband.bandcamp.com/album/five-more-flowers&quot;&gt;Five More Flowers&lt;/a&gt; [Woodsist]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, a few earworms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Acid Arab — &lt;a href=&quot;https://www.youtube.com/watch?v=e8Wc6_Q6tIA&quot;&gt;Atlas&lt;/a&gt; (feat. Cem Yıldız)&lt;/li&gt;
&lt;li&gt;Arca &amp;amp; Tokischa — &lt;a href=&quot;https://www.youtube.com/watch?v=fhsfy-nLapA&quot;&gt;Chama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Beirut — &lt;a href=&quot;https://www.youtube.com/watch?v=aaQLHfuXF2A&quot;&gt;Caspian Tiger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Björk &amp;amp; Rosalía — &lt;a href=&quot;https://www.youtube.com/watch?v=8jsi2Tgvx6A&quot;&gt;Oral&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Brian Eno — &lt;a href=&quot;https://www.youtube.com/watch?v=U9D2BJ5tv8o&quot;&gt;All I Remember&lt;/a&gt; (from the &lt;a href=&quot;https://brianeno.lnk.to/EnoSoundtrack&quot;&gt;Eno &lt;abbr&gt;OST&lt;/abbr&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Childish Gambino — &lt;a href=&quot;https://www.youtube.com/watch?v=yD9Xo0V-hPw&quot;&gt;Lithonia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Erika Isac — &lt;a href=&quot;https://www.youtube.com/watch?v=C0Bc0nwSerM&quot;&gt;Macarena&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Jamie &lt;abbr&gt;XX&lt;/abbr&gt; — &lt;a href=&quot;https://jamiexx.bandcamp.com/track/its-so-good&quot;&gt;It’s So Good&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Jon Hopkins &amp;amp; Ólafur Arnalds — &lt;a href=&quot;https://www.youtube.com/watch?v=708gIm6BP20&quot;&gt;Forever Held&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Keaton Henson &amp;amp; Daniel Herskedal — &lt;a href=&quot;https://www.youtube.com/watch?v=CbqeSnhARfk&quot;&gt;Try&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ladaniva — &lt;a href=&quot;https://www.youtube.com/watch?v=vGGbp88ziXU&quot;&gt;Saraiman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sevdaliza — &lt;a href=&quot;https://www.youtube.com/watch?v=qVqFuokjRMc&quot;&gt;Alibi&lt;/a&gt; (feat. Pabllo Vittar &amp;amp; Yseult)&lt;/li&gt;
&lt;li&gt;Sevdaliza — &lt;a href=&quot;https://www.youtube.com/watch?v=X1H5sOc_14U&quot;&gt;Ride Or Die, pt. 2&lt;/a&gt; (feat. Villano Antillano &amp;amp; Tokischa)&lt;/li&gt;
&lt;li&gt;The Dare — &lt;a href=&quot;https://www.youtube.com/watch?v=YXXbck3y5C8&quot;&gt;Girls&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Live shows.&lt;/strong&gt; Khruangbin, Massive Attack, and Tigran Hamasyan in Cluj. Tindersticks in Barcelona.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This has been the 11th edition of my favorite records. Timeline: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other 2024 lists: &lt;a href=&quot;https://boomkat.com/charts/boomkat-end-of-year-charts-2024/2689&quot;&gt;Boomkat&lt;/a&gt;, &lt;a href=&quot;https://bleep.com/top-10-albums-of-the-year-2024&quot;&gt;Bleep&lt;/a&gt;, &lt;a href=&quot;https://www.roughtrade.com/en-de/collection/albums-of-the-year-2024&quot;&gt;Rough Trade&lt;/a&gt;, &lt;a href=&quot;https://thequietus.com/tq-charts/albums-of-the-year/albums-of-the-year-best-albums-2024-norman-records/&quot;&gt;The Quietus&lt;/a&gt;, &lt;a href=&quot;https://www.thewire.co.uk/audio/tracks/the-wire-s-releases-of-the-year-2024&quot;&gt;The Wire&lt;/a&gt;, &lt;a href=&quot;https://hicks.design/journal/hicks-design-s-music-of-2024&quot;&gt;Jon Hicks&lt;/a&gt;, &lt;a href=&quot;https://colly.com/journal/twenty-twentyfour-in-music&quot;&gt;Simon Collison&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My “Skip to content” markup was breaking the back button on iOS</title>
		<link href="https://danburzo.ro/skip-to-content-back-button/" />
		<updated>2024-04-13T00:00:00Z</updated>
		<id>https://danburzo.ro/skip-to-content-back-button/</id>
		<content type="html"
			>&lt;p&gt;One of the great joys of having your own website is being able to publish all sorts of random pages, such as this &lt;a href=&quot;https://danburzo.ro/reference/dom-events/&quot;&gt;listing of &lt;abbr&gt;DOM&lt;/abbr&gt; events&lt;/a&gt;, because why not.&lt;/p&gt;
&lt;p&gt;Perusing these sort of reference pages I built for myself, it became increasingly grating that the basic feature of maintaining the scroll position during navigation seemed to be broken on iOS Safari.&lt;/p&gt;
&lt;p&gt;I checked: the issue didn’t affect &lt;em&gt;all&lt;/em&gt; websites, just my three-&lt;abbr&gt;HTML&lt;/abbr&gt;-files-in-a-trenchoat website. Whenever I followed an external link and tapped the &lt;em&gt;Back&lt;/em&gt; button, Safari frustratingly scrolled to the top of my original page, instead of keeping my place on the page.&lt;/p&gt;
&lt;figure&gt;
	&lt;video controls=&quot;&quot; width=&quot;443&quot; height=&quot;860&quot; style=&quot;max-width: 20em; margin: 0 auto; display: block;&quot; poster=&quot;https://danburzo.ro/img/skip-to-content-back-button/ios-back-button.png&quot;&gt;
		&lt;source src=&quot;https://danburzo.ro/img/skip-to-content-back-button/ios-back-button.mp4&quot; /&gt;
	&lt;/video&gt;
&lt;figcaption&gt;
&lt;p&gt;Video: I open an article on my website about my favorite albums from last year. I scroll down to Altın Gün and tap the link, which takes me to the album’s Bandcamp page. When I tap the &lt;em&gt;Back&lt;/em&gt; button, the article is scrolled back to the &lt;code&gt;h1&lt;/code&gt; heading.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;The problem: &lt;code&gt;&amp;lt;main tabindex=-1&amp;gt;&lt;/code&gt; loses your scroll position&lt;/h2&gt;
&lt;p&gt;A key detail in the video is that Safari doesn’t scroll the page all the way back to the top, but to the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; heading. This suggests it has some sort of heuristic that leads it to believe that’s where the focus/scroll should be.&lt;/p&gt;
&lt;p&gt;I started by looking at how desktop browsers place the focus when returning to the page, by inspecting &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement&quot;&gt;&lt;code&gt;document.activeElement&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firefox returns the focus to the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element that produced the navigation;&lt;/li&gt;
&lt;li&gt;Chrome returns the focus to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element;&lt;/li&gt;
&lt;li&gt;Safari returns the focus to the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That Safari focuses the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element was a clue to what might be going on with the mobile version.&lt;/p&gt;
&lt;p&gt;Turns out my &lt;em&gt;Skip to content&lt;/em&gt; markup — more specifically, the &lt;code&gt;tabindex=-1&lt;/code&gt; on the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element — is what makes Safari &lt;strong&gt;scroll the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element’s top edge into view&lt;/strong&gt; when you return from a navigation.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#main&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Skip to content&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
…
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;main&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;tabindex&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;…&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Removing the &lt;code&gt;tabindex&lt;/code&gt; from the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element made iOS Safari behave.&lt;/p&gt;
&lt;p&gt;Is the attribute needed in the first place? To include &lt;code&gt;tabindex&lt;/code&gt; has long &lt;a href=&quot;https://cerovac.com/a11y/2024/04/wcag-bypass-blocks-skip-to-content-improve-user-interaction-speed-and-even-prevent-pain/&quot;&gt;been recommended&lt;/a&gt; to ensure that a variety of browsers and assistive technology move the focus properly to the destination element. A host of issues that &lt;code&gt;&amp;lt;main tabindex=-1&amp;gt;&lt;/code&gt; is supposed to solve have been fixed in the meantime, but &lt;a href=&quot;https://www.tpgi.com/how-to-avoid-breaking-web-pages-for-keyboard-users/&quot;&gt;not all of them&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It seems some sort of manual focus management is still in order, and without any obvious documented downsides, the pattern remains popular. But breaking the scroll position is a pretty significant drawback, one that warrants a re-evaluation of this approach, at least until Safari fixes the issue.&lt;/p&gt;
&lt;h2&gt;The solution: to be discussed&lt;/h2&gt;
&lt;p&gt;For now, I have removed the &lt;code&gt;tabindex&lt;/code&gt; attribute, and will be looking for an alternate solution. Ultimately, it &lt;a href=&quot;https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk&quot;&gt;might involve JavaScript&lt;/a&gt;, as for the pattern used on &lt;a href=&quot;https://www.gov.uk/&quot;&gt;gov.uk&lt;/a&gt;, or changing the anchor to point to an element inside &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, such as the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I have also &lt;a href=&quot;https://danburzo.ro/demos/main-tabindex.html&quot;&gt;made a test case&lt;/a&gt; and submitted &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=272624&quot;&gt;WebKit#272624&lt;/a&gt; about it. Testing in Browserstack reveals that the issue was introduced somewhere around iOS/Safari 14 back in 2020.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update March 2026:&lt;/strong&gt; Manuel Matuzović tests whether &lt;code&gt;tabindex&lt;/code&gt; is still required and concludes &lt;a href=&quot;https://matuzo.at/blog/2026/skip-links-tabindex?d=0503&quot;&gt;we may be able to omit the attribute&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>How to think about HTML responsive images</title>
		<link href="https://danburzo.ro/responsive-images-html/" />
		<updated>2024-04-06T00:00:00Z</updated>
		<id>https://danburzo.ro/responsive-images-html/</id>
		<content type="html"
			>&lt;p&gt;The days with an immobilized knee are long and I’ve just read through the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/images.html&quot;&gt;Images section&lt;/a&gt; of the &lt;abbr&gt;HTML&lt;/abbr&gt; Standard, as one does, hoping to better understand how responsive images work.&lt;/p&gt;
&lt;h2&gt;What’s a responsive image?&lt;/h2&gt;
&lt;p&gt;The term &lt;em&gt;responsive image&lt;/em&gt; encompasses two complementary approaches.&lt;/p&gt;
&lt;p&gt;In the context of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design#responsive_imagesmedia&quot;&gt;responsive web design&lt;/a&gt;, a responsive image is one that’s made fluid with the &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;aspect-ratio&lt;/code&gt;, and &lt;code&gt;object-*&lt;/code&gt; properties, as part of &lt;abbr&gt;CSS&lt;/abbr&gt; layouts that change with the viewport size and other media conditions.&lt;/p&gt;
&lt;p&gt;But what the &lt;abbr&gt;HTML&lt;/abbr&gt; Standard is concerned with, and what this article talks about, are the so-called “adaptive images” enabled by the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset&quot;&gt;&lt;code&gt;srcset&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes&quot;&gt;&lt;code&gt;sizes&lt;/code&gt;&lt;/a&gt; attributes on &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, and the dedicated &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source&quot;&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;&lt;/a&gt; elements. These &lt;abbr&gt;HTML&lt;/abbr&gt; features help the browser pick the most appropriate image &lt;em&gt;content&lt;/em&gt; for the current environment.&lt;/p&gt;
&lt;p&gt;Here’s how I made sense of responsive image content, progressing from simpler to more complicated — and then back to simple.&lt;/p&gt;
&lt;style type=&quot;text/css&quot;&gt;
	.img-wrapper {
		display: flex;
		flex-wrap: wrap; 
		justify-content: center;
		gap: 1rem; 
		align-items: center;
	}

	.img-wrapper img {
		max-width: initial;
	}
&lt;/style&gt;
&lt;h2&gt;Level 1: one image, one resolution&lt;/h2&gt;
&lt;p&gt;Let’s start with the simplest image markup:&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; /&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;A black and white pupper, glancing inquisitively&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;In the absence of any attributes or styles to dictate otherwise, &lt;code&gt;puppy.jpg&lt;/code&gt; renders at its natural width and height, &lt;code&gt;120px&lt;/code&gt; by &lt;code&gt;150px&lt;/code&gt; in &lt;abbr&gt;CSS&lt;/abbr&gt; pixels.&lt;/p&gt;
&lt;p&gt;A &lt;abbr&gt;CSS&lt;/abbr&gt; pixel is &lt;a href=&quot;https://drafts.csswg.org/css-values/#reference-pixel&quot;&gt;defined&lt;/a&gt; as &lt;q&gt;the visual angle of one pixel on a device with a device pixel density of 96dpi and a distance from the reader of an arm’s length&lt;/q&gt; or about 0.0213 degrees.&lt;/p&gt;
&lt;p&gt;Modern phones are used at a much smaller distance than arm’s length. Their screens need to have a greater density to look good: the visual angle of 0.0213° at a distance of 50 centimeters computes to a pixel on a 137ppi (pixels per inch) display.&lt;/p&gt;
&lt;p&gt;The 2556×1179px physical resolution of an iPhone 15, packed in a display that measures 6.1 inches diagonally, gives it a pixel density of 460 ppi. That’s more than three times denser than our nominal &lt;abbr&gt;CSS&lt;/abbr&gt; display. The iPhone can therefore comfortably use three device pixels to draw each &lt;abbr&gt;CSS&lt;/abbr&gt; pixel, and web content will have more or less the same size as when viewed on a regular monitor placed further away.&lt;/p&gt;
&lt;p&gt;This ratio between a device pixel and a &lt;abbr&gt;CSS&lt;/abbr&gt; pixel is called the &lt;em&gt;device pixel ratio&lt;/em&gt; and is available on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio&quot;&gt;&lt;code&gt;window.devicePixelRatio&lt;/code&gt;&lt;/a&gt;. The device pixel ratio is not a fixed measure of the capabilities of the physical display. The display resolution chosen by the user, or zooming in and out of the web page, can also influence the ratio. Zooming into the page makes for fewer, larger &lt;abbr&gt;CSS&lt;/abbr&gt; pixels, so the device pixel ratio increases.&lt;/p&gt;
&lt;p&gt;Back to our image. On devices that have a device pixel ratio of 2 or 3 (usually called &lt;a href=&quot;https://en.wikipedia.org/wiki/Retina_display&quot;&gt;retina displays&lt;/a&gt; regardless of brand), the image looks blurry at its natural size: the display can potentially use two or three separate device pixels in the space of a &lt;abbr&gt;CSS&lt;/abbr&gt; pixel, but we only give it one image pixel to draw. On these denser displays, the image looks better when scaled down to increase its density to match that of the display. When one image pixel becomes one device pixel, instead of two or three, the image is as crisp as it gets.&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; width=&quot;120&quot; /&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; width=&quot;60&quot; /&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; width=&quot;40&quot; /&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;120&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 1x --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;  &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;60&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 2x --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;  &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;40&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 3x --&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;The same &lt;code&gt;120px&lt;/code&gt; by &lt;code&gt;150px&lt;/code&gt; image is rendered at its natural size, then scaled down in half, and finally to 1/3 of its natural width.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The rendered size of an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element can be adjusted with the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; &lt;abbr&gt;HTML&lt;/abbr&gt; attributes, or with equivalent &lt;abbr&gt;CSS&lt;/abbr&gt; properties.&lt;/p&gt;
&lt;div id=&quot;width-height-important&quot;&gt;
&lt;p&gt;It’s best to &lt;a href=&quot;https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/&quot;&gt;include explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes&lt;/a&gt; on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;. That way the browser can leave room for the image beforehand and prevent layout shifts as the image loads. It also helps presentation in contexts that don’t ship the author’s &lt;abbr&gt;CSS&lt;/abbr&gt;, such as &lt;abbr&gt;RSS&lt;/abbr&gt; feeds. (I can count at least three feeds in my reader with huge, distracting icons that are, no doubt, stray images without sizing attributes.)&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Level 2: one image, many resolutions&lt;/h2&gt;
&lt;p&gt;For a sharp image on displays of various densities, shrinking the same image file to various degrees to increase its density is insufficient. We need commensurately larger images that pack more detail.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy-sizes.png&quot; alt=&quot;The same image of our pupper at three different scales, one next to each other&quot; width=&quot;2003&quot; height=&quot;1303&quot; /&gt;
&lt;figcaption&gt;
&lt;p&gt;Images with progressively larger resolutions, representing the same content.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; width=&quot;120&quot; /&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy-hd.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; width=&quot;120&quot; /&gt;
&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy-ultra-hd.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; width=&quot;120&quot; /&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;120&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 1x --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy-hd.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;120&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 2x --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy-ultra-hd.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;120&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 3x --&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
When shrunk to their ideal density, all three images render at the same size.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset&quot;&gt;&lt;code&gt;srcset&lt;/code&gt;&lt;/a&gt; attribute lets you pack all the images on a single &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag, and let the browser choose the most appropriate for each situation.&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
	&lt;img srcset=&quot;https://danburzo.ro/img/responsive-images/puppy-ultra-hd.jpg 3x, https://danburzo.ro/img/responsive-images/puppy-hd.jpg 2x&quot; src=&quot;https://danburzo.ro/img/responsive-images/puppy.jpg&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; /&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy-ultra-hd.jpg 3x, puppy-hd.jpg 2x&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;figcaption&gt;
&lt;/figcaption&gt;
&lt;p&gt;Each entry in &lt;code&gt;srcset&lt;/code&gt; has a pixel density descriptor: a floating-point number followed by the unit &lt;code&gt;x&lt;/code&gt;. The descriptor next to each image source declares the image density at which that source is meant to be rendered. If omitted for a source, a &lt;code&gt;1x&lt;/code&gt; descriptor is assumed.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;src&lt;/code&gt; attribute on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; is both a fallback for browsers that don’t support &lt;code&gt;srcset&lt;/code&gt; (a vanishingly small lot) and a contribution to the set of image source candidates with an implicit &lt;code&gt;1x&lt;/code&gt; pixel density descriptor.&lt;/p&gt;
&lt;p&gt;The browser will select the most appropriate image source out of the set of candidates, based not only on the display density, but possibly other factors such as network speed and mobile data preferences. This choice is made &lt;q&gt;in an implementation-defined manner&lt;/q&gt;, meaning the browser is free to choose whatever it thinks works best.&lt;/p&gt;
&lt;p&gt;In the absence of &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; &lt;abbr&gt;HTML&lt;/abbr&gt; attributes to dictate otherwise, whatever image source is selected will be rendered at its &lt;em&gt;density-corrected natural dimensions&lt;/em&gt;, which are the image’s natural dimensions divided by its declared density. The density-corrected natural dimensions can be accessed on the &lt;abbr&gt;DOM&lt;/abbr&gt; object’s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalWidth&quot;&gt;&lt;code&gt;naturalWidth&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalHeight&quot;&gt;&lt;code&gt;naturalHeight&lt;/code&gt;&lt;/a&gt; properties:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;naturalWidth &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; intrinsicWidth &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; density&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;naturalHeight &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; intrinsicHeight &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; density&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At &lt;code&gt;1x&lt;/code&gt; density,  the element’s density-corrected natural dimensions correspond to the file’s natural (intrinsic) dimensions. At &lt;code&gt;2x&lt;/code&gt; density, the image is rendered at half the number of &lt;abbr&gt;CSS&lt;/abbr&gt; pixels as the file’s natural dimensions.&lt;/p&gt;
&lt;p&gt;Because of the way we’ve generated our three images and declared their intended densities, they all render at a width of &lt;code&gt;120px&lt;/code&gt;, regardless of which image source the browser chooses. So there’s no issue with adding the recommended explicit width and height attributes:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy-ultra-hd.jpg 3x, puppy-hd.jpg 2x&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;120&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;150&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To tell which image source the browser has chosen at any given point, look at the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/currentSrc&quot;&gt;&lt;code&gt;currentSrc&lt;/code&gt;&lt;/a&gt; property on the image element.&lt;/p&gt;
&lt;h2&gt;Level 3: dynamic image density&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;srcset&lt;/code&gt; attribute with pixel density descriptors works well for images that are meant to be displayed at their (density-corrected) natural size.&lt;/p&gt;
&lt;p&gt;But images often participate in responsive layouts and are made fluid with &lt;abbr&gt;CSS&lt;/abbr&gt;, so an image renders at various densities depending on the layout. On a large screen, the image may be part of a three-column layout. On a smaller screen, the layout may collapse to a single column with full-width images.&lt;/p&gt;
&lt;p&gt;This is a case where the image density changes but the display density doesn’t, so &lt;code&gt;srcset&lt;/code&gt; with density descriptors won’t cut it.&lt;/p&gt;
&lt;p&gt;There’s a second way we can use &lt;code&gt;srcset&lt;/code&gt;. To help the browser choose an image source of appropriate density when media conditions change, we can swap our density descriptors for a combination of &lt;em&gt;width descriptors&lt;/em&gt; and a separate &lt;code&gt;sizes&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;Instead of describing the intended image densities, width descriptors (using the &lt;code&gt;w&lt;/code&gt; suffix) declare the natural (intrinsic) width of each of the image sources.&lt;/p&gt;
&lt;p&gt;This information, by itself, is not enough for the browser to make a meaningful choice. It needs to know how the image is going to be laid out. This is accomplished with the &lt;code&gt;sizes&lt;/code&gt; attribute, which declares the layout width of the image in one or more media conditions.&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
	&lt;img srcset=&quot;https://danburzo.ro/img/responsive-images/puppy-ultra-hd.jpg 360w, https://danburzo.ro/img/responsive-images/puppy-hd.jpg 240w, https://danburzo.ro/img/responsive-images/puppy.jpg 120w&quot; sizes=&quot;(min-width: 50em) 10em, 80vw&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; /&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;
		 puppy-ultra-hd.jpg 360w,
		 puppy-hd.jpg 240w,
		 puppy.jpg 120w
	 &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(min-width: 400px) 10em, 80vw&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;The &lt;code&gt;srcset&lt;/code&gt; attribute declares three image sources using width (&lt;code&gt;w&lt;/code&gt;) descriptors: one &lt;code&gt;360px&lt;/code&gt; wide, one &lt;code&gt;240px&lt;/code&gt; wide, and a final one &lt;code&gt;120px&lt;/code&gt; wide. The &lt;code&gt;sizes&lt;/code&gt; attribute describes how the image is laid out: on devices wider than 400px, they are rendered at a width of &lt;code&gt;10em&lt;/code&gt;, otherwise they occupy 80% of the viewport width.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We’re free to declare the layout width with any &lt;abbr&gt;CSS&lt;/abbr&gt; unit for &lt;code&gt;&amp;lt;length&amp;gt;&lt;/code&gt;, and use &lt;code&gt;calc()&lt;/code&gt; and other &lt;a href=&quot;https://drafts.csswg.org/css-values/#math-function&quot;&gt;math functions&lt;/a&gt;, to try to &lt;em&gt;roughly&lt;/em&gt; match the image’s actual layout width.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Percentages aren’t allowed in the &lt;code&gt;sizes&lt;/code&gt; attribute, as they wouldn’t match the usual understanding of &lt;em&gt;percentage of the parent’s width&lt;/em&gt;. Remember that choosing image sources for &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/loading&quot;&gt;eagerly-loaded&lt;/a&gt; images happens &lt;em&gt;before&lt;/em&gt; layout, so we can only refer to things known beforehand, such as the viewport’s dimensions.&lt;/p&gt;
&lt;p&gt;As we’ll see later on, lazily-loaded images, which are fetched &lt;em&gt;after&lt;/em&gt; layout, &lt;a href=&quot;https://danburzo.ro/responsive-images-html/#sizes-auto&quot;&gt;don’t need to juggle&lt;/a&gt; any of this &lt;code&gt;sizes&lt;/code&gt; stuff.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The purpose of the &lt;code&gt;sizes&lt;/code&gt; attribute is to help convert width descriptors to density descriptors. Width descriptors are turned into density descriptors by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;identifying the size that matches the current media conditions among the values in &lt;code&gt;sizes&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;resolving the size value to &lt;abbr&gt;CSS&lt;/abbr&gt; pixels;&lt;/li&gt;
&lt;li&gt;dividing the declared width by that amount of pixels.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The density computed from a source’s width descriptor and a layout size is called the source’s &lt;em&gt;effective density&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In a larger viewport that’s, say, 1920 pixels wide, our image is intended to be displayed at a width of &lt;code&gt;10em&lt;/code&gt;, which computes to &lt;code&gt;10 * 16px = 160px&lt;/code&gt; in &lt;abbr&gt;CSS&lt;/abbr&gt; pixels. The large image source, having a declared width of &lt;code&gt;360px&lt;/code&gt;, when rendered &lt;code&gt;160px&lt;/code&gt; wide, will have an effective density of  &lt;code&gt;360/160 = 2.25&lt;/code&gt;. The medium and small images will have effective densities of &lt;code&gt;1.5&lt;/code&gt; and &lt;code&gt;0.75&lt;/code&gt; respectively. On this viewport width, the equivalent &lt;code&gt;srcset&lt;/code&gt; with density descriptors is:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;
		 puppy-ultra-hd.jpg 2.25x,
		 puppy-hd.jpg 1.5x,
		 puppy.jpg 0.75x
	 &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10em&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a smaller viewport that is 300 pixels wide, our images are meant to be displayed at &lt;code&gt;80vw&lt;/code&gt;, which computes to &lt;code&gt;300px * 80/100 = 240px&lt;/code&gt; &lt;abbr&gt;CSS&lt;/abbr&gt; pixels. In these media conditions, our three image sources will have effective densities of &lt;code&gt;1.5&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, and &lt;code&gt;0.5&lt;/code&gt; respectively. On this smaller viewport width, the equivalent &lt;code&gt;srcset&lt;/code&gt; with density descriptors is:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;
		 puppy-ultra-hd.jpg 1.5x,
		 puppy-hd.jpg 1x,
		 puppy.jpg 0.5x
	 &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 80vw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Therefore, &lt;code&gt;srcset&lt;/code&gt; with width descriptors, combined with &lt;code&gt;sizes&lt;/code&gt;, is a way to assign a &lt;strong&gt;dynamic density&lt;/strong&gt; to image sources, roughly based on how the image is laid out in various media conditions.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;srcset&lt;/code&gt; uses width descriptors, the image’s &lt;code&gt;src&lt;/code&gt; is purely a fallback for browsers that don’t support it. The attribute can’t contribute an image source, because there’s no way to attach a width descriptor to its value.&lt;/p&gt;
&lt;p id=&quot;srcset-flavors&quot;&gt;
&lt;/p&gt;&lt;p&gt;The two &lt;code&gt;srcset&lt;/code&gt; flavors are both ultimately resolved to a set of image sources with density descriptors, but they don’t mix well. You can’t use width descriptors for some sources and density descriptors for others in a single &lt;code&gt;srcset&lt;/code&gt;. Either use width descriptors &lt;em&gt;with&lt;/em&gt; the &lt;code&gt;sizes&lt;/code&gt; attribute, or density descriptors &lt;em&gt;without&lt;/em&gt; the &lt;code&gt;sizes&lt;/code&gt; attribute. In the former case, &lt;code&gt;sizes&lt;/code&gt; is necessary; in the latter, it serves no purpose and is ignored.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don’t rely on the default.&lt;/strong&gt; When we say &lt;code&gt;sizes&lt;/code&gt; is necessary for width descriptors, it means a &lt;abbr&gt;HTML&lt;/abbr&gt; document omitting it &lt;a href=&quot;https://validator.w3.org/&quot;&gt;won’t validate&lt;/a&gt; and it won’t be &lt;em&gt;canon&lt;/em&gt;. But &lt;abbr&gt;HTML&lt;/abbr&gt; is tolerant of author errors and defaults to a value of &lt;code&gt;100vw&lt;/code&gt;. As Eric Portis &lt;a href=&quot;https://alistapart.com/blog/post/article-update-dont-rely-on-default-sizes/&quot;&gt;explains&lt;/a&gt;, you don’t want to rely on that default, as it potentially nudges the browser to fetch images much larger than needed, defeating the whole purpose of the feature.&lt;/p&gt;
&lt;p&gt;An exception to the requirement are &lt;a href=&quot;https://danburzo.ro/responsive-images-html/#sizes-auto&quot;&gt;lazy-loaded, auto-sized images&lt;/a&gt;, for which omitting the &lt;code&gt;sizes&lt;/code&gt; attribute on any &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element is equivalent to &lt;code&gt;sizes=&amp;quot;auto&amp;quot;&lt;/code&gt; rather than &lt;code&gt;sizes=&amp;quot;100vw&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Level 4: the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;srcset&lt;/code&gt; attribute on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; merely provides a set of candidate sources to the browser, along with enough information about them to allow for an informed choice. As Mat Marquis writes in the &lt;a href=&quot;https://web.dev/learn/images/&quot;&gt;Learn Images&lt;/a&gt; course, it makes &lt;code&gt;srcset&lt;/code&gt; a &lt;a href=&quot;https://web.dev/learn/images/descriptive&quot;&gt;descriptive syntax&lt;/a&gt;. It says to the browser: &lt;q&gt;here’s what I have, now &lt;em&gt;you&lt;/em&gt; pick!&lt;/q&gt;&lt;/p&gt;
&lt;p&gt;There’s another &lt;abbr&gt;HTML&lt;/abbr&gt; feature with which we can be &lt;a href=&quot;https://web.dev/learn/images/prescriptive&quot;&gt;more prescriptive&lt;/a&gt; and say &lt;q&gt;only consider these image sources if these conditions are met&lt;/q&gt;. This is done with one or more &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source&quot;&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;&lt;/a&gt; elements associated with the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; by virtue of being wrapped together in a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;&lt;/a&gt; element:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element is a container that augments its inner &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; by providing more sets of image sources to choose from, declared with &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements. If the browser doesn’t support these elements, no harm is done: they’re ignored and the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; works as if it were alone.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;(You might say &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; is the original &lt;a href=&quot;https://adactio.com/journal/20618&quot;&gt;&lt;abbr&gt;HTML&lt;/abbr&gt; web component&lt;/a&gt;, but that’s an angle for another day.)&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Like image elements, &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;s use the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes to declare their set of image sources. In addition, &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements accept two attributes that condition their contribution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;type&lt;/code&gt; attribute declares the media type of the image set, so that the browser can skip image formats it doesn’t understand;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;media&lt;/code&gt; attribute declares the media conditions where the image set makes sense, which the browser skips if they don’t apply.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first source that matches the current media conditions and media type capabilities defines the set of image candidates that’s supplied to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. The browser chooses the most appropriate image from that set, just as if the source’s &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes had been declared on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;If no sources apply to the current circumstances, the image’s own &lt;code&gt;srcset&lt;/code&gt; or &lt;code&gt;src&lt;/code&gt; is used as a fallback.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;type&lt;/code&gt; attribute&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; attribute enables us to serve newer, more efficient image formats to supporting browsers without ruining it for the others. If a browser can’t use &lt;code&gt;image/avif&lt;/code&gt;, or &lt;code&gt;image/webp&lt;/code&gt; it can just ignore the respective &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;
&lt;figure&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;puppy.avif&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;image/avif&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;puppy.webp&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;image/webp&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;puppy.jpg&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;Serving newer image formats safely with &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements with a &lt;code&gt;type&lt;/code&gt; attribute. If we omitted the &lt;code&gt;type&lt;/code&gt; attribute, or used &lt;code&gt;puppy.avif&lt;/code&gt; and &lt;code&gt;puppy.webp&lt;/code&gt; directly in the image’s &lt;code&gt;srcset&lt;/code&gt; attribute, unsuspecting older browsers would fetch formats they don’t understand, resulting in a broken image.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3&gt;The &lt;code&gt;media&lt;/code&gt; attribute&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;media&lt;/code&gt; attribute can contain any media condition. We could, for example, serve an alternative image for dark mode, and a higher-contrast version appropriate for printing.&lt;/p&gt;
&lt;figure&gt;
	&lt;div class=&quot;img-wrapper&quot;&gt;
		&lt;img src=&quot;https://danburzo.ro/img/responsive-images/macos-light.png&quot; width=&quot;200&quot; alt=&quot;The Display settings in MacOS, rendered in light mode.&quot; /&gt;
		&lt;img src=&quot;https://danburzo.ro/img/responsive-images/macos-dark.png&quot; width=&quot;200&quot; alt=&quot;The Display settings in MacOS, rendered in dark mode.&quot; /&gt;
		&lt;img src=&quot;https://danburzo.ro/img/responsive-images/macos-contrast.png&quot; width=&quot;200&quot; alt=&quot;The Display settings in MacOS, rendered in high-contrast mode.&quot; /&gt;
	&lt;/div&gt;
&lt;figcaption&gt;
&lt;p&gt;Three styles for illustrating a portion of the macOS display settings: light mode, dark mode, and high-contrast mode.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
&lt;picture&gt;
	&lt;source media=&quot;(prefers-color-scheme: dark)&quot; srcset=&quot;https://danburzo.ro/img/responsive-images/macos-dark.png&quot; /&gt;
	&lt;source media=&quot;print&quot; srcset=&quot;https://danburzo.ro/img/responsive-images/macos-contrast.png&quot; /&gt;
	&lt;img style=&quot;max-width: 100%&quot; src=&quot;https://danburzo.ro/img/responsive-images/macos-light.png&quot; alt=&quot;The Display settings in MacOS, rendered in light mode.&quot; /&gt;
&lt;/picture&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;(prefers-color-scheme: dark)&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;macos-dark.png&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;print&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;macos-contrast.png&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;macos-light.png&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;A &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element uses the three styles in &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements, with the appropriate media queries. Above the code: Live picture, as chosen by the browser. Try switching to dark mode, or print-previewing the page to see the effect.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The same rules for &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; apply to &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements: you can’t mix density descriptors and width descriptors in a single &lt;code&gt;srcset&lt;/code&gt;, and you must use the &lt;code&gt;sizes&lt;/code&gt; attribute with, and only with, width descriptors.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements also have rules of their own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;each &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; must generally have some sort of &lt;code&gt;type&lt;/code&gt; or &lt;code&gt;media&lt;/code&gt; condition, or both, attached to it. Only if the image itself doesn’t have a &lt;code&gt;srcset&lt;/code&gt; already, one bare, conditionless &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; is allowed.&lt;/li&gt;
&lt;li&gt;there’s no &lt;code&gt;src&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; because &lt;a href=&quot;https://github.com/ResponsiveImagesCG/picture-element/issues/78&quot;&gt;it would be confusing&lt;/a&gt; and besides, any valid &lt;code&gt;src&lt;/code&gt; can be plopped into &lt;code&gt;srcset&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Level 5: art direction&lt;/h2&gt;
&lt;p&gt;While a &lt;code&gt;srcset&lt;/code&gt; is meant to represent the same image content at different scales, multiple &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements can represent different content altogether. In the previous example, we used the &lt;code&gt;media&lt;/code&gt; attribute to serve images styled according to user preferences.&lt;/p&gt;
&lt;p&gt;The different images don’t need to have the same aspect ratio. In fact, there’s nothing stopping us from serving radically different images in various scenarios. On a large screen, a photograph could be a wide shot of the subject, while on smaller screens that can be cropped closer to the action.&lt;/p&gt;
&lt;p&gt;The technique is often called art-directing responsive images.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy-art-direction.png&quot; alt=&quot;An image of the pupper with a square around its face to indicate a close-up image crops&quot; width=&quot;640&quot; height=&quot;530&quot; /&gt;
&lt;figcaption&gt;
Art directing an image: a close-up crop of 200px by 200px is chosen for smaller screens.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Since setting the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element &lt;a href=&quot;https://danburzo.ro/responsive-images-html/#width-height-important&quot;&gt;is important&lt;/a&gt;, it seemed like a great idea to &lt;a href=&quot;https://github.com/whatwg/html/issues/4968&quot;&gt;add support&lt;/a&gt; for &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes on &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements. The dimensions can then be imparted to the image element when the source is selected. (The case for sources also getting &lt;a href=&quot;https://github.com/whatwg/html/issues/6627&quot;&gt;their own &lt;code&gt;alt&lt;/code&gt; attribute&lt;/a&gt; is still being made.)&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
	&lt;picture&gt;
		&lt;source srcset=&quot;https://danburzo.ro/img/responsive-images/puppy-closeup.jpg&quot; width=&quot;200&quot; height=&quot;200&quot; media=&quot;(max-width: 40em)&quot; /&gt;
		&lt;img src=&quot;https://danburzo.ro/img/responsive-images/puppy-ultra-hd.jpg&quot; width=&quot;450&quot; height=&quot;600&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; /&gt;
	&lt;/picture&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy-closeup.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;200&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;200&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(max-width: 40em)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;puppy-ultra-hd.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;450&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;600&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;An art-directed responsive image displays a close-up of the subject when the viewport is less than &lt;code&gt;40em&lt;/code&gt; (&lt;code&gt;640px&lt;/code&gt;) wide. Notice the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes on the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cropping after image load:&lt;/strong&gt; Images can also be art-directed with &lt;abbr&gt;CSS&lt;/abbr&gt; using the &lt;code&gt;object-fit&lt;/code&gt;, &lt;code&gt;object-position&lt;/code&gt;, and &lt;code&gt;object-view-box&lt;/code&gt; properties. However, to the extent that is practical, using &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; to serve the pre-cropped images saves some bandwidth and compute energy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;sizes-auto&quot;&gt;Extra credit: lazy images with &lt;code&gt;sizes=auto&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;As promised in the introduction, we end with a bit of respite from the complexity of responsive image markup.&lt;/p&gt;
&lt;p&gt;A recent addition to the &lt;abbr&gt;HTML&lt;/abbr&gt; Standard allows lazily-loaded images to ditch the arduous, hand-coded, approximated values in the &lt;code&gt;sizes&lt;/code&gt; attribute. Instead, with the &lt;code&gt;auto&lt;/code&gt; value, the browser uses the image’s actual layout width to compute more accurate densities for the image candidates.&lt;/p&gt;
&lt;p&gt;Eric Portis &lt;a href=&quot;https://ericportis.com/posts/2023/auto-sizes-pretty-much-requires-width-and-height/&quot;&gt;covers the feature and its caveats&lt;/a&gt; in admirable detail, so I won’t repeat the points here. But below is the markup for an image that’s sized at a height of &lt;code&gt;20vh&lt;/code&gt; whose appropriate source is selected without us specifying the layout widths manually.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We &lt;a href=&quot;https://danburzo.ro/responsive-images-html/#srcset-flavors&quot;&gt;noted earlier&lt;/a&gt; that the &lt;code&gt;sizes&lt;/code&gt; attribute is generally mandatory on any &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element that uses width descriptors in the &lt;code&gt;srcset&lt;/code&gt; attribute. For lazy-loaded, auto-sized images, however, we can also omit the &lt;code&gt;sizes&lt;/code&gt; attribute on preceding &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements, and it will be interpreted as equivalent to &lt;code&gt;sizes=&amp;quot;auto&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
&lt;div class=&quot;img-wrapper&quot;&gt;
	&lt;img srcset=&quot;https://danburzo.ro/img/responsive-images/puppy-ultra-hd.jpg 360w, https://danburzo.ro/img/responsive-images/puppy-hd.jpg 240w, https://danburzo.ro/img/responsive-images/puppy.jpg 120w&quot; style=&quot;height: 20vh;width: auto&quot; width=&quot;240&quot; height=&quot;300&quot; alt=&quot;A black and white pupper, glancing inquisitively&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; /&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;
		 puppy-ultra-hd.jpg 360w,
		 puppy-hd.jpg 240w,
		 puppy.jpg 120w
	 &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;240&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;300&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 20vh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	 &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;
&lt;p&gt;Using &lt;code&gt;sizes=auto&lt;/code&gt; is only possible when the image is lazy-loaded via the &lt;code&gt;loading=lazy&lt;/code&gt; attribute. The way auto-sizing is implemented in the HTML specification, some way to restore the underlying image’s aspect ratio is required. The &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes work well and are also recommended to prevent layout shifts.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;How browsers choose one image&lt;/h2&gt;
&lt;p&gt;Now that we’ve covered the theory of providing image source candidates, let’s find out how browsers actually pick the most appropriate one.&lt;/p&gt;
&lt;p&gt;I’ve run a couple of quick tests on MacBook Pro and iPhone, and dipped into browser source code to confirm the behavior (insofar as I am looking at the right code to begin with).&lt;/p&gt;
&lt;h3&gt;Density descriptors in &lt;code&gt;srcset&lt;/code&gt;&lt;/h3&gt;
&lt;figure&gt;
	&lt;img style=&quot;display: block; margin: 0 auto;&quot; srcset=&quot;https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.1x.png 0.1x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.2x.png 0.2x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.3x.png 0.3x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.4x.png 0.4x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.5x.png 0.5x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.6x.png 0.6x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.7x.png 0.7x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.8x.png 0.8x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@0.9x.png 0.9x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1x.png 1x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.1x.png 1.1x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.2x.png 1.2x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.3x.png 1.3x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.4x.png 1.4x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.5x.png 1.5x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.6x.png 1.6x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.7x.png 1.7x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.8x.png 1.8x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@1.9x.png 1.9x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@2x.png 2x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@2.2x.png 2.2x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@2.4x.png 2.4x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@2.6x.png 2.6x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@2.8x.png 2.8x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@3x.png 3x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@3.2x.png 3.2x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@3.4x.png 3.4x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@3.6x.png 3.6x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@3.8x.png 3.8x, https://danburzo.ro/demos/responsive-images/placeholders/300x200@4x.png 4x&quot; alt=&quot;A placeholder image displaying its size and density, as chosen by the browser&quot; /&gt;	
&lt;figcaption&gt;
&lt;p&gt;The image above uses several image sources of &lt;code&gt;300×200px&lt;/code&gt; constant size, but labeled and declared with density descriptors ranging from &lt;code&gt;0.1x&lt;/code&gt; to &lt;code&gt;4x&lt;/code&gt; in the &lt;code&gt;srcset&lt;/code&gt; attribute. This shows us which source the browser picks. &lt;a href=&quot;https://danburzo.ro/demos/responsive-images/density.html&quot;&gt;Test: image density selection&lt;/a&gt;.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Firefox.&lt;/strong&gt; At &lt;code&gt;100%&lt;/code&gt; zoom, the device pixel ratio is &lt;code&gt;2&lt;/code&gt;. Zooming in and out of the page updates the &lt;abbr&gt;DPR&lt;/abbr&gt; and re-fetches the image with the smallest density that’s higher than the current &lt;abbr&gt;DPR&lt;/abbr&gt;, or the highest density available when all densities are too small. Relevant code in &lt;a href=&quot;https://github.com/mozilla/gecko-dev/blob/46dae934738073c4f51c2b628503fe3bf84f89d2/dom/base/ResponsiveImageSelector.cpp#L331&quot;&gt;&lt;code&gt;Responsive&lt;wbr /&gt;Image&lt;wbr /&gt;Selector.cpp&lt;wbr /&gt;#L331&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chrome.&lt;/strong&gt; The browser selects the image with the density closest to the &lt;abbr&gt;DPR&lt;/abbr&gt; when loading the page. Zooming in and out of the page updates the &lt;abbr&gt;DPR&lt;/abbr&gt;, but the browser will only fetch another, more appropriate image on page refresh. It will also prefer the densest image source it has in its cache, even if its density is much higher than needed. Relevant code in &lt;a href=&quot;https://github.com/chromium/chromium/blob/3167b39925e4ea2c1983d049c8a440d023a727bb/third_party/blink/renderer/core/html/parser/html_srcset_parser.cc#L424&quot;&gt;&lt;code&gt;html&lt;wbr /&gt;_srcset&lt;wbr /&gt;_parser.cc&lt;wbr /&gt;#L424&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Safari.&lt;/strong&gt; The &lt;abbr&gt;DPR&lt;/abbr&gt; is fixed to a value of &lt;code&gt;2&lt;/code&gt; on the MacBook and &lt;code&gt;3&lt;/code&gt; on the iPhone. Zooming in and out of the page doesn’t update the &lt;abbr&gt;DPR&lt;/abbr&gt; or fetch another image. Like in Firefox, you get the image with the smallest density that’s higher than this fixed &lt;abbr&gt;DPR&lt;/abbr&gt;. Relevant code in &lt;a href=&quot;https://github.com/WebKit/WebKit/blob/4e262b07baa1b55186d79a67b35f326cfdea48b2/Source/WebCore/html/parser/HTMLSrcsetParser.cpp#L266&quot;&gt;&lt;code&gt;HTML&lt;wbr /&gt;Srcset&lt;wbr /&gt;Parser.cpp&lt;wbr /&gt;#L266&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All in all, given the browser algorithms, every image source is evaluated, and the order in &lt;code&gt;srcset&lt;/code&gt; doesn’t affect the choice. Do keep in mind that only the first item for each particular density (be it declared density or effective density) is kept, and any duplicates are pruned.&lt;/p&gt;
&lt;p&gt;As far as I can tell, browsers aren’t currently applying any of the sophisticated decision-making envisioned by the &lt;abbr&gt;HTML&lt;/abbr&gt; Standard. An image density close to the current &lt;abbr&gt;DPR&lt;/abbr&gt; is always favored.&lt;/p&gt;
&lt;p&gt;With the exception of Firefox, which responds to zooming, browsers stick to their choice of image source throughout the page session. Pinch-zooming does not affect &lt;abbr&gt;DPR&lt;/abbr&gt; in any browser, so raster images don’t get magically enhanced if you pinch into them (this is &lt;a href=&quot;https://drafts.csswg.org/cssom-view/#dom-window-devicepixelratio&quot;&gt;by design&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Width descriptors in &lt;code&gt;srcset&lt;/code&gt;&lt;/h3&gt;
&lt;figure&gt;
	&lt;img style=&quot;display: block; margin: 0 auto;&quot; srcset=&quot;https://danburzo.ro/demos/responsive-images/placeholders/300x200.png 300w, https://danburzo.ro/demos/responsive-images/placeholders/600x400.png 600w, https://danburzo.ro/demos/responsive-images/placeholders/900x600.png 900w, https://danburzo.ro/demos/responsive-images/placeholders/1200x800.png 1200w, https://danburzo.ro/demos/responsive-images/placeholders/1500x1000.png 1500w, https://danburzo.ro/demos/responsive-images/placeholders/1800x1200.png 1800w, https://danburzo.ro/demos/responsive-images/placeholders/2100x1400.png 2100w, https://danburzo.ro/demos/responsive-images/placeholders/2400x1600.png 2400w, https://danburzo.ro/demos/responsive-images/placeholders/2700x1800.png 2700w, https://danburzo.ro/demos/responsive-images/placeholders/3000x2000.png 3000w&quot; alt=&quot;A placeholder image displaying its size, as chosen by the browser&quot; /&gt;	
&lt;figcaption&gt;
&lt;p&gt;The image above uses several image sources with the same aspect ratio but different scales, labeled and declared with width descriptors ranging from &lt;code&gt;300w&lt;/code&gt; to &lt;code&gt;3000w&lt;/code&gt; in the &lt;code&gt;srcset&lt;/code&gt;. The declared layout width of the image is &lt;code&gt;sizes=&amp;quot;100vw&amp;quot;&lt;/code&gt;. This shows us which image source the browser picks.&lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;Note:&lt;/strong&gt; To make it fit in the article’s layout, the image is made responsive with &lt;code&gt;max-width: 100%; height: auto&lt;/code&gt;. Open the test in a separate tab to evaluate more accurately: &lt;a href=&quot;https://danburzo.ro/demos/responsive-images/width.html&quot;&gt;Test: image width selection&lt;/a&gt;)&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When using width descriptors in &lt;code&gt;srcset&lt;/code&gt;, we expect the browser to factor in the &lt;code&gt;sizes&lt;/code&gt; attribute (here having a value of &lt;code&gt;100vw&lt;/code&gt;) to compute density descriptors that update along with the viewport. It’s no surprise then that browsers behave more or less like with density descriptors, with the added benefit that the image sources get re-evaluated more often.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Firefox.&lt;/strong&gt; Resizing the browser window causes the browser to choose the image with the appropriate effective density at any given moment. Zooming in and out of the page re-evaluates things but generally doesn’t produce any effect: while the size of the &lt;abbr&gt;CSS&lt;/abbr&gt; pixel increases (and with it, the &lt;abbr&gt;DPR&lt;/abbr&gt;), &lt;code&gt;100vw&lt;/code&gt; evaluates to fewer &lt;abbr&gt;CSS&lt;/abbr&gt; pixels, which results in more or less constant image density throughout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chrome.&lt;/strong&gt; Like in Firefox, resizing the browser window causes the browser to re-evaluate the image sources based on their effective density. As seen with the density descriptor test, Chrome caches the images it fetches and always uses the densest available. Once fetched for a large viewport, a dense image will be used even as you shrink the viewport.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Safari&lt;/strong&gt; is the most conservative about fetching other images. With &lt;code&gt;sizes=&amp;quot;100vw&amp;quot;&lt;/code&gt;, the image source is evaluated once on page load, and resizing the browser window has no effect. With an attribute that contains media conditions, such as &lt;code&gt;sizes=&amp;quot;(max-width: 400px) 25vw, (max-width: 800px) 50vw, 100vw&amp;quot;&lt;/code&gt;, it re-evaluates the image sources once a different size applies.&lt;/p&gt;
&lt;p&gt;Safari’s approach means &lt;code&gt;sizes=&amp;quot;100vw&amp;quot;&lt;/code&gt; does not make an image fluid like in the other browsers, which update the density-corrected natural dimensions after each resize. The dimensions are only computed once, when the source is first rendered.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I haven’t included more elaborate browser tests because they make my brain hurt, but we’ve hopefully made sense of how &lt;abbr&gt;HTML&lt;/abbr&gt; responsive images are specified to work and got a glimpse of how current browsers choose image sources.&lt;/p&gt;
&lt;p&gt;Responsive images have been available in &lt;abbr&gt;HTML&lt;/abbr&gt; for a decade. They have been written about extensively, often covering the same ground and angle. Here are some pointers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/learn/images/&quot;&gt;Learn images&lt;/a&gt; by Mat Marquis&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ericportis.com/posts/2014/srcset-sizes/&quot;&gt;Srcset and sizes&lt;/a&gt; (2014) by Eric Portis&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloudfour.com/thinks/responsive-images-101-definitions/&quot;&gt;Responsive Images 101&lt;/a&gt; (2015), a ten-part series (and &lt;a href=&quot;https://cloudfour.com/thinks/announcing-our-new-ebook-responsive-images-101/&quot;&gt;book&lt;/a&gt;) by Jason Grigsby&lt;/li&gt;
&lt;li&gt;Eric Portis’s Observable notebook &lt;a href=&quot;https://observablehq.com/@eeeps/w-descriptors-and-sizes-under-the-hood&quot;&gt;&lt;code&gt;w&lt;/code&gt; descriptors and &lt;code&gt;sizes&lt;/code&gt;: Under the hood&lt;/a&gt; explains these concepts and behaviors with nice-looking, interactive browser tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Changelog&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;August 2025.&lt;/strong&gt; Added some sample markup for &lt;code&gt;sizes=&amp;quot;auto&amp;quot;&lt;/code&gt; and clarified that an omitted &lt;code&gt;sizes&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements for an lazy-loaded, auto-sized image is equivalent to &lt;code&gt;sizes=&amp;quot;auto&amp;quot;&lt;/code&gt; rather than &lt;code&gt;sizes=&amp;quot;100vw&amp;quot;&lt;/code&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2023</title>
		<link href="https://danburzo.ro/favorite-records-2023/" />
		<updated>2023-12-23T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2023/</id>
		<content type="html"
			>&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; I will be updating this list beyond 2023. The additions are marked with the manicule (☞).&lt;/p&gt;
&lt;h2&gt;Heavy rotation&lt;/h2&gt;
&lt;p&gt;Some of the albums I’ve listened to a lot this year, in alphabetical order.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alternativ Quartet — Deocamdată suntem [&lt;a href=&quot;https://alternativquartet.bandcamp.com/album/deocamdat-suntem&quot;&gt;s/r&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Alternativ Quartet — Departe de solstițiu [&lt;a href=&quot;https://alternativquartet.bandcamp.com/album/departe-de-solsti-iu&quot;&gt;s/r&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Arvo Pärt — Tractus [&lt;a href=&quot;https://ecmrecords.com/product/arvo-part-tractus-estonian-philharmonic-chamber-choir-tallinn-chamber-orchestra-tonu-kaljuste/&quot;&gt;&lt;abbr&gt;ECM&lt;/abbr&gt;&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Balmorhea — Pendant World [&lt;a href=&quot;https://balmorheamusic.com/&quot;&gt;Deutsche Grammophon&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Baxter Dury — I Thought I was Better than You [&lt;a href=&quot;https://baxterdury.bandcamp.com/album/i-thought-i-was-better-than-you&quot;&gt;Heavenly&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Beirut — Hadsel [&lt;a href=&quot;https://beirut.lnk.to/Hadsel&quot;&gt;Pompeii&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Ben Howard — Is it? [&lt;a href=&quot;https://benhowardisit.bandcamp.com/album/is-it&quot;&gt;Island&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;The Blaze — Jungle [&lt;a href=&quot;https://theblaze.bfan.link/jungle-album.web&quot;&gt;Animal63&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Blonde Redhead — Sit Down for Dinner [&lt;a href=&quot;https://blonderedhead.bandcamp.com/album/sit-down-for-dinner&quot;&gt;Section1&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Brian Eno &amp;amp; Fred again.. — Secret Life [&lt;a href=&quot;https://lnk.to/TEXT055&quot;&gt;Text&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Bruce Brubaker — Eno Piano [&lt;a href=&quot;https://bruce-brubaker.bandcamp.com/album/eno-piano&quot;&gt;InFiné&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Constant Smiles — Kenneth Anger [&lt;a href=&quot;https://constantsmiles.bandcamp.com/album/kenneth-anger&quot;&gt;Sacred Bones&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Devendra Banhart — Flying Wig [&lt;a href=&quot;https://devendrabanhart.bandcamp.com/album/flying-wig&quot;&gt;Mexican Summer&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;El Michels Affair &amp;amp; Black Thought — Glorious Game [&lt;a href=&quot;https://elmichelsaffair.bandcamp.com/album/glorious-game&quot;&gt;Big Crown&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Eluvium — (Whirring Marvels In) Consensus Reality [&lt;a href=&quot;https://eluvium.bandcamp.com/album/whirring-marvels-in-consensus-reality&quot;&gt;Temporary Residence&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Erland Cooper &amp;amp; Scottish Ensemble — Folded Landscapes [&lt;a href=&quot;https://erlandcooper.lnk.to/FoldedLandscapes&quot;&gt;Mercury KX&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Fever Ray — Radical Romantics [&lt;a href=&quot;https://feverray.bandcamp.com/album/radical-romantics&quot;&gt;Rabid&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Gregory Alan Isakov — Appaloosa Bones [&lt;a href=&quot;https://gregoryalanisakov.bandcamp.com/album/appaloosa-bones&quot;&gt;s/r&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;☞ Iuliad — Mă așteptam la alt sfârșit [s/r]&lt;/li&gt;
&lt;li&gt;James Blake — Playing Robots Into Heaven [&lt;a href=&quot;https://jamesblake.lnk.to/PRIH&quot;&gt;Republic/Polydor&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;☞ James Holden — Imagine This Is A High Dimensional Space Of All Possibilities [&lt;a href=&quot;https://jamesholden.bandcamp.com/album/imagine-this-is-a-high-dimensional-space-of-all-possibilities&quot;&gt;Border Community&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Jay-Jay Johanson — Fetish [ART/29 Music]&lt;/li&gt;
&lt;li&gt;Jonathan Bree — Pre-Code Hollywood [&lt;a href=&quot;https://jonathanbree.bandcamp.com/album/pre-code-hollywood&quot;&gt;Lil’ Chief&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Kelela — Raven [&lt;a href=&quot;https://kelela.bandcamp.com/album/raven&quot;&gt;Warp&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;King Creosote — &lt;span class=&quot;sc&quot;&gt;I DES&lt;/span&gt; [&lt;a href=&quot;https://kingcreosote.bandcamp.com/album/i-des&quot;&gt;Domino&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Laurel Halo — Atlas [&lt;a href=&quot;https://laurelhalo.bandcamp.com/album/atlas&quot;&gt;Awe&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Lubomyr Melnyk — The Sacred Thousand [&lt;a href=&quot;https://jersikarecords.bandcamp.com/album/the-sacred-thousand&quot;&gt;Jersika&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;The National — First Two Pages of Frankenstein [&lt;a href=&quot;https://thenational.bandcamp.com/album/first-two-pages-of-frankenstein&quot;&gt;4AD&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;The National — Laugh Track [&lt;a href=&quot;https://thenational.bandcamp.com/album/laugh-track&quot;&gt;4AD&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Philip Selway — Strange Dance [&lt;a href=&quot;https://philipselway.bandcamp.com/album/strange-dance&quot;&gt;Bella Union&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Roger Eno — The Skies, they shift like chords… [&lt;a href=&quot;https://store.deutschegrammophon.com/p51-i0028948650217/roger-eno/the-skies-they-shift-like-chords/index.html&quot;&gt;Deutsche Grammophon&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Romy — Mid Air [&lt;a href=&quot;https://romyromyromy.bandcamp.com/album/mid-air&quot;&gt;Young&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Sigur Rós — Átta [&lt;a href=&quot;https://sigurros.bandcamp.com/album/tta&quot;&gt;Von Dur&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Sofia Kourtesis — Madres [&lt;a href=&quot;https://sofiakourtesis.bandcamp.com/album/madres-2&quot;&gt;Ninja Tune&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Yara Asmar — Synth Waltzes and Accordion Laments [&lt;a href=&quot;https://yaraasmar.bandcamp.com/album/synth-waltzes-and-accordion-laments&quot;&gt;Hive Mind&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Quite into Daniel Lanois’s &lt;a href=&quot;https://daniellanois.lnk.to/playerpiano&quot;&gt;Player, Piano&lt;/a&gt; (from &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;) and &lt;a href=&quot;https://enzofavata.bandcamp.com/album/enzo-favata-the-crossing-2&quot;&gt;Enzo Favata The Crossing&lt;/a&gt; (from &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;) as well, both of which I learned about just this year.&lt;/p&gt;
&lt;h2&gt;On the radar&lt;/h2&gt;
&lt;p&gt;These ones I enjoyed but didn’t get to dig into as much as I’d hoped.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;All Hands_Make Light — “Darling The Dawn” [&lt;a href=&quot;https://allhandsmakelight.bandcamp.com/album/darling-the-dawn&quot;&gt;Constellation&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Altın Gün — Aşk [&lt;a href=&quot;https://altingun.bandcamp.com/album/a-k&quot;&gt;Glitterbeat&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Alva Noto — HYbr:ID II [&lt;a href=&quot;https://noton.info/product/n-061/&quot;&gt;Noton&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Alva Noto — Kinder der Sonne [&lt;a href=&quot;https://noton.info/product/n-058/&quot;&gt;Noton&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Alva Noto — This Stolen Country of Mine [&lt;a href=&quot;https://noton.info/product/n-059/&quot;&gt;Noton&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Angel Olsen — Forever Means [&lt;a href=&quot;https://angelolsen.bandcamp.com/album/forever-means&quot;&gt;Jagjaguwar&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;&lt;span class=&quot;sc&quot;&gt;ANOHNI&lt;/span&gt; — My Back Was a Bridge For You To Cross [&lt;a href=&quot;https://anohni.bandcamp.com/album/my-back-was-a-bridge-for-you-to-cross-2&quot;&gt;Rough Trade&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Anoushka Shankar — Chapter I: Forever, for Now [&lt;a href=&quot;https://anoushkashankar.bandcamp.com/album/chapter-i-forever-for-now&quot;&gt;Leiter&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Aphex Twin — Blackbox Life Recorder 21f / In a Room7 F760 [&lt;a href=&quot;https://aphextwin.bandcamp.com/album/blackbox-life-recorder-21f-in-a-room7-f760&quot;&gt;Warp&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;bdrmm — I Don&#39;t Know [&lt;a href=&quot;https://bdrmm.bandcamp.com/album/i-dont-know&quot;&gt;Rock Action&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;boygenius — the record [&lt;a href=&quot;https://xboygeniusx.bandcamp.com/album/the-record&quot;&gt;Interscope&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Carlos Cipa — Ourselves, as we are [&lt;a href=&quot;https://carloscipa.bandcamp.com/album/ourselves-as-we-are&quot;&gt;Warner Classics&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Chris Abrahams, Oren Ambarchi, Robbie Avenaim — Placelessness [&lt;a href=&quot;https://orenambarchi.bandcamp.com/album/placelessness&quot;&gt;Ideologic Organ&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Colleen — Le Jour Et La Nuit Du Réel [&lt;a href=&quot;https://colleencolleen.bandcamp.com/album/le-jour-et-la-nuit-du-r-el&quot;&gt;Thrill Jockey&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Daniel Avery — More Truth [&lt;a href=&quot;https://danielavery.bandcamp.com/album/more-truth&quot;&gt;&lt;abbr&gt;PIAS&lt;/abbr&gt;&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;David Toop &amp;amp; Lawrence English — The Shell that Speaks the Sea [&lt;a href=&quot;https://lawrenceenglish.bandcamp.com/album/the-shell-that-speaks-the-sea&quot;&gt;Room40&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Donato Dozzy &amp;amp; Sabla — Crono [&lt;a href=&quot;https://gangofducks.bandcamp.com/album/crono&quot;&gt;Gang of Ducks&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Eiko Ishibashi &amp;amp; Jim O’Rourke — Lifetime of a Flower [&lt;a href=&quot;https://eikoishibashijimorourke.bandcamp.com/album/lifetime-of-a-flower&quot;&gt;Week-End&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;☞ Explosions in the Sky — End [&lt;a href=&quot;https://explosionsinthesky.bandcamp.com/album/end&quot;&gt;Temporary Residence&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Föllakzoid — V [&lt;a href=&quot;https://follakzoid.bandcamp.com/album/v&quot;&gt;Sacred Bones&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Forest Swords — Bolted [&lt;a href=&quot;https://forestswords.bandcamp.com/album/bolted&quot;&gt;Ninja Tunes&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Greta Van Fleet — Starcatcher [&lt;a href=&quot;https://gvf.lnk.to/starcatcher&quot;&gt;Republic&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Hania Rani — Ghosts [&lt;a href=&quot;https://haniarani.bandcamp.com/album/ghosts&quot;&gt;Gondwana&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Hilary Woods — Acts of Light [&lt;a href=&quot;https://hilarywoodsmusic.bandcamp.com/album/acts-of-light&quot;&gt;Sacred Bones&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;James Ellis Ford — The Hum [&lt;a href=&quot;https://jamesellisford.bandcamp.com/album/the-hum&quot;&gt;Warp&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Jana Horn — The Window Is the Dream [&lt;a href=&quot;https://janahorn.bandcamp.com/album/the-window-is-the-dream&quot;&gt;No Quarter&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Jim O’Rourke — Hands that Bind OST [&lt;a href=&quot;https://jimorourke.bandcamp.com/album/hands-that-bind-original-motion-picture-soundtrack&quot;&gt;Drag City&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Julie Byrne — The Greater Wings [&lt;a href=&quot;https://juliembyrne.bandcamp.com/album/the-greater-wings&quot;&gt;Ghostly&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Julie Byrne — Julie Byrne with Laugh Cry Laugh [&lt;a href=&quot;https://juliembyrne.bandcamp.com/album/julie-byrne-with-laugh-cry-laugh&quot;&gt;Ghostly&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Kali Malone ft. Stephen O’Malley &amp;amp; Lucy Railton — Does Spring Hide Its Joy [&lt;a href=&quot;https://kalimalone.bandcamp.com/album/does-spring-hide-its-joy&quot;&gt;Ideologic Organ&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Kevin Morby — More Photographs (A Continuum) [&lt;a href=&quot;https://kevinmorby.bandcamp.com/album/more-photographs-a-continuum&quot;&gt;Dead Oceans&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Lawrence English &amp;amp; Lea Bertucci — Chthonic [&lt;a href=&quot;https://lawrenceenglishleabertucci.bandcamp.com/album/chthonic&quot;&gt;American Dreams&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Lawrence English &amp;amp; Werner Dafeldecker — Tropic of Capricorn [&lt;a href=&quot;https://hallowground.bandcamp.com/album/lawrence-english-werner-dafeldecker-tropic-of-capricorn&quot;&gt;Hallow Ground&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Loscil &amp;amp; Lawrence English — Colours of Air [&lt;a href=&quot;https://loscil.bandcamp.com/album/colours-of-air&quot;&gt;Kranky&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Luke Schneider — It Is Solved By Walking [&lt;a href=&quot;https://lukeschneider.bandcamp.com/album/it-is-solved-by-walking&quot;&gt;Third Man&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Natalia Beylis — Mermaids [&lt;a href=&quot;https://nataliabeylis.bandcamp.com/album/mermaids&quot;&gt;Touch Sensitive&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Noriko Tujiko — Crépuscule I &amp;amp; II [&lt;a href=&quot;https://tujikonoriko1.bandcamp.com/album/cr-puscule-i-ii&quot;&gt;Editions Mego&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Oneohtrix Point Never — Again [&lt;a href=&quot;https://oneohtrixpointnever.bandcamp.com/album/again&quot;&gt;Warp&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Oren Ambarchi &amp;amp; Eric Thielemans — Double Consciousness [&lt;a href=&quot;https://orenambarchi.bandcamp.com/album/double-consciousness&quot;&gt;Matière Mémoire&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Ozmotic &amp;amp; Fennesz — Senzatempo [&lt;a href=&quot;https://ozmotic.bandcamp.com/album/senzatempo&quot;&gt;Touch&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Philip Jeck &amp;amp; Chris Watson — Oxmardyke [&lt;a href=&quot;https://philipjeck.bandcamp.com/album/oxmardyke&quot;&gt;Touch&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;PJ Harvey — I Inside the Old Year Dying [&lt;a href=&quot;https://pjharvey.bandcamp.com/album/i-inside-the-old-year-dying&quot;&gt;Partisan&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Rone, Orchestre National de Lyon &amp;amp; Dirk Brossé — L(oo)ping [&lt;a href=&quot;https://rone-music.bandcamp.com/album/l-oo-ping&quot;&gt;InFiné&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Ryuichi Sakamoto — 12 [Milan/Commmons]&lt;/li&gt;
&lt;li&gt;Róisín Murphy — Hit Parade [&lt;a href=&quot;https://roisinmurphy.bandcamp.com/album/hit-parade&quot;&gt;Ninja Tune&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Sarah Davachi — Long Gradus [&lt;a href=&quot;https://sarahdavachi.bandcamp.com/album/long-gradus&quot;&gt;Late Music&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Slowdive — everything is alive [&lt;a href=&quot;https://slowdive.bandcamp.com/album/everything-is-alive&quot;&gt;Dead Oceans&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Sufjan Stevens — Javelin [&lt;a href=&quot;https://sufjanstevens.bandcamp.com/album/javelin&quot;&gt;Asthmatic Kitty&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Tim Hecker — No Highs [&lt;a href=&quot;https://timhecker.bandcamp.com/album/no-highs&quot;&gt;Kranky&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Timber Timbre — Lovage [&lt;a href=&quot;https://lnk.to/Lovage_Timber_Timbre&quot;&gt;Hot Dreams&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Vanessa Wagner — Les heures immobiles [&lt;a href=&quot;https://vanessa-wagner.bandcamp.com/album/les-heures-immobiles&quot;&gt;InFiné&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Will Butler + Sister Squares — Will Butler + Sister Squares [&lt;a href=&quot;https://willbutler.bandcamp.com/album/will-butler-sister-squares&quot;&gt;Merge&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Yann Novak — The Voice of Theseus [&lt;a href=&quot;https://yannnovak.bandcamp.com/album/the-voice-of-theseus-2&quot;&gt;Touch/Fairwood&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Young Fathers — Heavy Heavy [&lt;a href=&quot;https://youngfathersofficial.bandcamp.com/album/heavy-heavy&quot;&gt;Ninja Tunes&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Other things&lt;/h2&gt;
&lt;p&gt;Reissues, expansions, archival material, remixes, live albums, et cetera:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Iceland Symphony Orchestra, Daniel Bjarnason — Jóhannsson: A Prayer To The Dynamo and Suites from “Sicario” &amp;amp; “The Theory of Everything” [&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/a-prayer-to-the-dynamo-johannsson-13031&quot;&gt;Deutsche Grammophon&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Joanna Brouk — Sounds of the Sea (1981) [&lt;a href=&quot;https://numerogroup.com/products/sounds-of-the-sea&quot;&gt;Numero&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Laraaji — Segue To Infinity [&lt;a href=&quot;https://laraajinumero.bandcamp.com/album/segue-to-infinity&quot;&gt;Numero&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Roger Eno — The Turning Year: Rarities [&lt;a href=&quot;https://www.rogereno.com/product/798403/the-turning-year-rarities&quot;&gt;Deutsche Grammophon&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Sarah Davachi — Selected Works I + II [&lt;a href=&quot;https://sarahdavachi.bandcamp.com/album/selected-works-i-ii&quot;&gt;Late Music/Disciples&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Sparklehorse — Bird Machine [&lt;a href=&quot;https://sparklehorse.bandcamp.com/album/bird-machine&quot;&gt;Anti&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;William Basinski — The Clocktower at the Beach (1979) [&lt;a href=&quot;https://lineimprint.bandcamp.com/album/the-clocktower-at-the-beach-1979&quot;&gt;Line&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Plus some individual tracks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Antlers — &lt;a href=&quot;https://theantlers.bandcamp.com/track/need-nothing&quot;&gt;Need Nothing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Björk ft. Rosalía — &lt;a href=&quot;https://www.youtube.com/watch?v=8jsi2Tgvx6A&quot;&gt;Oral&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Depeche Mode — &lt;a href=&quot;https://www.youtube.com/watch?v=iIyrLRixMs8&quot;&gt;Ghosts Again&lt;/a&gt; (from &lt;em&gt;Memento Mori&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;☞ Four Tet — &lt;a href=&quot;https://www.youtube.com/watch?v=DGaKVLFNWzs&quot;&gt;Three Drums&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Oneohtrix Point Never — &lt;a href=&quot;https://www.youtube.com/watch?v=_kyFqe36BqM&quot;&gt;A Barely Lit Path&lt;/a&gt; (from &lt;em&gt;Again&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Roger Eno, Cecily Eno, Lotti Eno — &lt;a href=&quot;https://www.youtube.com/watch?v=sWZi4ml1a4k&quot;&gt;Bells (With Voices)&lt;/a&gt; (from &lt;em&gt;The Turning Year: Rarities&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Live shows I went to this year: Shida Shahabi and A Winged Victory for the Sullen at the Barbican in London; Jay-Jay Johanson, Sigur Rós, Jamie XX, and Mulatu Astatke in Cluj; Florence and the Machine in Bucharest; Brian Eno and the Baltic Sea Orchestra in Utrecht.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;q&gt;&lt;a href=&quot;https://open.spotify.com/playlist/31OIme0YdF4ORWvEdTyE6V&quot;&gt;Ryuichi’s Last Playlist&lt;/a&gt;. We would like to share the playlist that Ryuichi had been privately compiling to be played at his own funeral to accompany his own passing. He truly was with music until the very end.&lt;/q&gt; — &lt;abbr&gt;RIP&lt;/abbr&gt; Ryuichi Sakamoto.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other 2023 lists: &lt;a href=&quot;https://boomkat.com/charts/boomkat-end-of-year-charts-2023&quot;&gt;Boomkat&lt;/a&gt;, &lt;a href=&quot;https://bleep.com/top-10-albums-of-the-year-2023&quot;&gt;Bleep&lt;/a&gt;, &lt;a href=&quot;https://www.roughtrade.com/en-gb/collection/albums-of-the-year-2023&quot;&gt;Rough Trade UK&lt;/a&gt;, &lt;a href=&quot;https://www.roughtrade.com/en-us/collection/albums-of-the-year-2023&quot;&gt;Rough Trade US&lt;/a&gt;, &lt;a href=&quot;https://thequietus.com/articles/33662-the-quietus-top-100-albums-of-2023-norman-records&quot;&gt;The Quietus&lt;/a&gt;, &lt;a href=&quot;https://www.thewire.co.uk/audio/tracks/the-wire-s-releases-of-the-year-2023&quot;&gt;The Wire&lt;/a&gt;. &lt;a href=&quot;https://colly.com/articles/twenty-twentythree-in-music&quot;&gt;Simon Collison&lt;/a&gt;, &lt;a href=&quot;https://hicks.design/journal/hicks-music-of-2023&quot;&gt;Jon Hicks&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>So you want to add a web feed</title>
		<link href="https://danburzo.ro/add-a-web-feed/" />
		<updated>2023-10-07T00:00:00Z</updated>
		<id>https://danburzo.ro/add-a-web-feed/</id>
		<content type="html"
			>&lt;p&gt;You’ve decided you want a &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_feed&quot;&gt;web feed&lt;/a&gt; for your website’s content.&lt;/p&gt;
&lt;p&gt;Some popular &lt;abbr&gt;CMS&lt;/abbr&gt;es have you covered with built-in feeds. For WordPress, an &lt;abbr&gt;RSS&lt;/abbr&gt; feed is available by default at &lt;code&gt;your-website.com/feed&lt;/code&gt;, so it’s just a matter of &lt;a href=&quot;https://danburzo.ro/add-a-web-feed/#link-to-feed&quot;&gt;making the feed visible&lt;/a&gt; in your &lt;abbr&gt;HTML&lt;/abbr&gt; templates and you’re good to go.&lt;/p&gt;
&lt;p&gt;But if you have to implement one from scratch, this guide goes through the basics. To keep it short, it has opinions without much space offered to alternatives and nuance.&lt;/p&gt;
&lt;h2&gt;Use the Atom format&lt;/h2&gt;
&lt;p&gt;The two most popular web feed formats are &lt;a href=&quot;https://en.wikipedia.org/wiki/RSS&quot;&gt;&lt;abbr&gt;RSS&lt;/abbr&gt;&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Atom_(web_standard)&quot;&gt;Atom&lt;/a&gt;, both &lt;abbr&gt;XML&lt;/abbr&gt;-based. The best format for new feeds is Atom. Here’s a minimal feed with one entry to illustrate the elements you need to use:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;feed&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2005/Atom&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Dan Burzo: Posts&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://danburzo.ro/posts.xml&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://danburzo.ro/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;updated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;2023-10-07T00:00:00Z&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;updated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;https://danburzo.ro/&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;author&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Dan Burzo&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;author&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;entry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;https://danburzo.ro/add-a-web-feed/&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;So you want to add a web feed for your website&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://danburzo.ro/add-a-web-feed/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;updated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;2023-10-07T00:00:00Z&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;updated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;content&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- entry content goes here --&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;entry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;feed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The feed has a set of fields at the top level, and another set of fields for each &lt;code&gt;&amp;lt;entry&amp;gt;&lt;/code&gt;. The guidelines below apply to both.&lt;/p&gt;
&lt;h3&gt;Good &lt;abbr&gt;ID&lt;/abbr&gt;s&lt;/h3&gt;
&lt;p&gt;&lt;abbr&gt;ID&lt;/abbr&gt;s are used to identify the feed and the entries. Feed readers use the entry &lt;abbr&gt;ID&lt;/abbr&gt; to remember when you’ve read or starred a post. Use the post’s &lt;abbr&gt;URL&lt;/abbr&gt; as the &lt;abbr&gt;ID&lt;/abbr&gt; for the corresponding entry, and the website’s &lt;abbr&gt;URL&lt;/abbr&gt; as the feed &lt;abbr&gt;ID&lt;/abbr&gt;, and then &lt;a href=&quot;https://www.w3.org/Provider/Style/URI.html&quot;&gt;try to never change them&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Date format&lt;/h3&gt;
&lt;p&gt;There’s a dizzying array of standard date/time formats, as shown in &lt;a href=&quot;https://ijmacd.github.io/rfc3339-iso8601/&quot;&gt;this diagram&lt;/a&gt; that illustrates &lt;abbr&gt;RFC 3339&lt;/abbr&gt; vs. &lt;abbr&gt;ISO 8601&lt;/abbr&gt; vs. &lt;abbr&gt;W3C&lt;/abbr&gt; formats. Atom narrows it down significantly: all dates in the feed must be in &lt;abbr&gt;RFC 3339&lt;/abbr&gt; date-time format with uppercase delimiters. That means we need all these stringed together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The date in &lt;code&gt;YYYY-MM-DD&lt;/code&gt; format, eg. &lt;code&gt;2023-10-07&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The uppercase &lt;code&gt;T&lt;/code&gt; separator&lt;/li&gt;
&lt;li&gt;The time in &lt;code&gt;HH:MM:SS&lt;/code&gt; format, eg. &lt;code&gt;21:30:00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Either the timezone offset, eg. &lt;code&gt;+03:00&lt;/code&gt;, or uppercase &lt;code&gt;Z&lt;/code&gt; for &lt;abbr&gt;UTC&lt;/abbr&gt; time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example: &lt;span style=&quot;color: var(--c-accent1);&quot;&gt;October 7th, 2023&lt;/span&gt; at 21:30, &lt;abbr&gt;UTC&lt;/abbr&gt; time is expressed as &lt;code&gt;2023-10-07T21:30:00Z&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The one date element that’s required for an entry is &lt;code&gt;&amp;lt;updated&amp;gt;&lt;/code&gt;. You can distinguish the initial publish date from the last update date by mixing in a &lt;code&gt;&amp;lt;published&amp;gt;&lt;/code&gt; element. On the top-level feed, &lt;code&gt;&amp;lt;updated&amp;gt;&lt;/code&gt; is the date of the latest change to any of the entries.&lt;/p&gt;
&lt;h3&gt;&lt;abbr&gt;HTML&lt;/abbr&gt; content&lt;/h3&gt;
&lt;p&gt;Since &lt;abbr&gt;XML&lt;/abbr&gt; and &lt;abbr&gt;HTML&lt;/abbr&gt; share the syntax to some extent, adding raw &lt;abbr&gt;HTML&lt;/abbr&gt; to the &lt;code&gt;&amp;lt;content&amp;gt;&lt;/code&gt; element will trip up the &lt;abbr&gt;XML&lt;/abbr&gt; parser. To prevent it, you can escape the &lt;abbr&gt;XML&lt;/abbr&gt;-sensitive characters &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;amp;&lt;/code&gt; to their named entities &lt;code&gt;&amp;amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;amp;amp;&lt;/code&gt; respectively.&lt;/p&gt;
&lt;p&gt;Or, skip the text processing and wrap the &lt;abbr&gt;HTML&lt;/abbr&gt; content as-is in a character data (&lt;abbr&gt;CDATA&lt;/abbr&gt;) section:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;content&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token cdata&quot;&gt;&amp;lt;![CDATA[ html content goes here ]]&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Careful: even ‘plain-text’ fields such as &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; can break &lt;abbr&gt;XML&lt;/abbr&gt; parsing if an unaccounted-for ampersand or &lt;code&gt;&amp;lt;&lt;/code&gt; ends up in it, so it’s a good idea to handle it similarly to &lt;code&gt;&amp;lt;content&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Absolute links&lt;/h3&gt;
&lt;p&gt;All links to resources must use absolute &lt;abbr&gt;URL&lt;/abbr&gt;s, or the feed must contain attributes that help feed readers resolve any relative &lt;abbr&gt;URL&lt;/abbr&gt;s they encounter.&lt;/p&gt;
&lt;p&gt;Technically, the Atom specification allows the &lt;code&gt;xml:base&lt;/code&gt; attribute on any feed element to define the base &lt;abbr&gt;URL&lt;/abbr&gt; against which to resolve relative &lt;abbr&gt;URL&lt;/abbr&gt;s within the scope of that element:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;content&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xml:&lt;/span&gt;base&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://danburzo.ro/add-a-web-feed/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token cdata&quot;&gt;&amp;lt;![CDATA[ 
	html content goes here 
]]&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How well &lt;code&gt;xml:base&lt;/code&gt; works in practice &lt;a href=&quot;https://github.com/Ranchero-Software/NetNewsWire/issues/3662&quot;&gt;depends on the feed reader&lt;/a&gt;. For best compatibility, put absolute &lt;abbr&gt;URL&lt;/abbr&gt;s everywhere, to the extent that your setup permits: the links to entries and the feed itself, and for all resources in the &lt;abbr&gt;HTML&lt;/abbr&gt; content.&lt;/p&gt;
&lt;p&gt;Replacing relative &lt;abbr&gt;URL&lt;/abbr&gt;s with absolute counterparts in &lt;abbr&gt;HTML&lt;/abbr&gt; content is not straightforward: it’s not just the &lt;code&gt;href&lt;/code&gt;s and the &lt;code&gt;src&lt;/code&gt;s, but things like &lt;code&gt;srcset&lt;/code&gt; that have their own little syntax going on. So it’s worth noting that feed readers are free to look at the entry’s &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;, along with the &lt;code&gt;xml:base&lt;/code&gt; attribute, to resolve relative &lt;abbr&gt;URL&lt;/abbr&gt;s. As long as everything higher level uses absolute &lt;abbr&gt;URL&lt;/abbr&gt;s, you’re probably fine to ship relative &lt;abbr&gt;URL&lt;/abbr&gt; inside the &lt;code&gt;&amp;lt;content&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Check that the feed is valid&lt;/h2&gt;
&lt;p&gt;You can paste your Atom feed content into the &lt;a href=&quot;https://validator.w3.org/feed/&quot;&gt;&lt;abbr&gt;W3C&lt;/abbr&gt; Feed Validator&lt;/a&gt; to check that everything has been generated correctly, or get very good guidance on fixing any error it runs into.&lt;/p&gt;
&lt;p&gt;After you’ve published your feed, add it to your feed reader to keep an eye. I use &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt; on Mac and iOS because it aligns with my idea of good software, but also because it’s relatively free of heuristics and hand-holding, so it tends to surface the quirks of an artisanal feed pretty quickly.&lt;/p&gt;
&lt;h2&gt;Put the feed on your server&lt;/h2&gt;
&lt;p&gt;Depending on your setup, the feed may be generated on the fly on a dedicated &lt;abbr&gt;URL&lt;/abbr&gt; as with WordPress’s &lt;code&gt;/feed/&lt;/code&gt;, or stored as a physical file such as &lt;code&gt;posts.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The correct media type for Atom feeds is &lt;code&gt;application/atom+xml&lt;/code&gt;, and you may have your server set the appropriate &lt;code&gt;Content-Type&lt;/code&gt; response header. Doing so provokes both useful and somewhat annoying behaviors for visitors depending on their browser, so it’s not a wholehearted recommendation.&lt;/p&gt;
&lt;p&gt;In the case of a physical &lt;abbr&gt;XML&lt;/abbr&gt; file such as &lt;code&gt;posts.xml&lt;/code&gt;, I find it works fine to serve it as a regular &lt;code&gt;application/xml&lt;/code&gt; file. Feed readers will figure it out.&lt;/p&gt;
&lt;h2 id=&quot;link-to-feed&quot;&gt;Link to the feed in &lt;abbr&gt;HTML&lt;/abbr&gt;&lt;/h2&gt;
&lt;p&gt;Now that the feed exists, all that’s left is to link to it. You can add a &lt;code&gt;&amp;lt;link rel=alternate&amp;gt;&lt;/code&gt; element in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, with the appropriate media type. This enables feed readers to extract the feed &lt;abbr&gt;URL&lt;/abbr&gt; from the web page, making it easiers for visitors to subscribe.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Dan Burzo&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;alternate&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;application/atom+xml&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;https://danburzo.ro/posts.xml&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although you can use more than one feed &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;, some readers will only pick the first they find and silently ignore the rest, so make sure the most important feed is first in line.&lt;/p&gt;
&lt;p&gt;For better discoverability, even if the feed refers to the &lt;code&gt;/blog&lt;/code&gt; section of your website, put the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; on all pages of your website, most importantly your homepage, which is how most users will try to add your website to their feed readers.&lt;/p&gt;
&lt;p&gt;Also include visible links to feeds in your site’s footer, labeled as such to make it findable with the browser’s search function:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;https://danburzo.ro/posts.xml&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Feed (Atom)&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these in place, man and machine alike can find your feeds.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;More things you can configure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.blogsareback.com/guides/enable-cors&quot;&gt;Enable &lt;abbr&gt;CORS&lt;/abbr&gt;&lt;/a&gt; for your feed, so web-based feed readers can fetch it directly (h/t &lt;a href=&quot;https://social.lol/@db/116141518328271914&quot;&gt;&lt;abbr&gt;DB&lt;/abbr&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc4287&quot;&gt;&lt;abbr&gt;RFC 4287&lt;/abbr&gt;: The Atom Syndication Format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kevincox.ca/2022/05/06/rss-feed-best-practices/&quot;&gt;&lt;abbr&gt;RSS&lt;/abbr&gt; Feed Best Practises&lt;/a&gt; by Kevin Cox&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://validator.w3.org/feed/docs/atom.html&quot;&gt;Introduction to Atom&lt;/a&gt; from the &lt;abbr&gt;W3C&lt;/abbr&gt; Feed Validator&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Slotted content in Eleventy</title>
		<link href="https://danburzo.ro/eleventy-slotted-content/" />
		<updated>2023-02-28T00:00:00Z</updated>
		<id>https://danburzo.ro/eleventy-slotted-content/</id>
		<content type="html"
			>&lt;p&gt;Some types of template data are awkward to maintain in &lt;a href=&quot;https://www.11ty.dev/docs/data-cascade/&quot;&gt;any of the many places&lt;/a&gt; from where Eleventy can read it. Markdown&#39;s front-matter data can hold simple pieces of information just fine, but becomes unwieldy for rich content.&lt;/p&gt;
&lt;p&gt;If you&#39;ve ever wanted to &lt;a href=&quot;https://daverupert.com/2021/01/art-direction-for-static-sites/&quot;&gt;art-direct individual pages&lt;/a&gt; with custom styles defined inline, I&#39;m sure you&#39;re not exactly thrilled with front-loading a wall of CSS-in-YAML:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;title: I wrote this on my portable typewriter
custom_style: |
  &amp;lt;style type=&#39;text/css&#39;&gt;
    body { font-family: monospace; }
  &amp;lt;/style&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The front-matter approach is workable, but has some drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you have to abide by weird YAML whitespace rules&lt;/li&gt;
&lt;li&gt;there&#39;s no syntax highlighting&lt;/li&gt;
&lt;li&gt;it places implementation details before the actual content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One solution to these annoyances, of which I&#39;ll try to convince you in this article, is to embed pieces of data in the content part of the Markdown file, that can then be referenced by name from the HTML layout, just like any other template data.&lt;/p&gt;
&lt;h2&gt;Adding slotted content to Eleventy&lt;/h2&gt;
&lt;p&gt;Say you&#39;re making a recipe website, and you want to put the ingredients and the instructions in different places in the HTML layout, without cluttering each Markdown file with presentational markup.&lt;/p&gt;
&lt;p&gt;Let&#39;s create a &lt;code&gt;{% slot %}&lt;/code&gt; &lt;a href=&quot;https://www.11ty.dev/docs/shortcodes/&quot;&gt;shortcode&lt;/a&gt; to define the recipe parts:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;title: &#39;Best pesto of your life&#39;
layout: layouts/recipe.njk&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

{% slot &#39;ingredients&#39; %}

&lt;span class=&quot;token list punctuation&quot;&gt;*&lt;/span&gt; fresh basil
&lt;span class=&quot;token list punctuation&quot;&gt;*&lt;/span&gt; pine nuts
&lt;span class=&quot;token list punctuation&quot;&gt;*&lt;/span&gt; olive oil
&lt;span class=&quot;token list punctuation&quot;&gt;*&lt;/span&gt; pecorino
&lt;span class=&quot;token list punctuation&quot;&gt;*&lt;/span&gt; garlic clove
&lt;span class=&quot;token list punctuation&quot;&gt;*&lt;/span&gt; salt

{% endslot %}

{% slot &#39;instructions&#39; %}

&lt;span class=&quot;token list punctuation&quot;&gt;1.&lt;/span&gt; Wash the basil leaves
&lt;span class=&quot;token list punctuation&quot;&gt;2.&lt;/span&gt; Grate the pecorino
&lt;span class=&quot;token list punctuation&quot;&gt;3.&lt;/span&gt; ...

{% endslot %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...that we can then place in the appropriate spots in the HTML layout:&lt;/p&gt;
&lt;pre class=&quot;language-twig&quot;&gt;&lt;code class=&quot;language-twig&quot;&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; title &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;layout&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;ingredients&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
			&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Ingredients&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
			&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ingredients &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; safe &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;instructions&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
			&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Instructions&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; 
			&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instructions &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; safe &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
 &lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 1: Add &lt;code&gt;slots&lt;/code&gt; data to each page&lt;/h3&gt;
&lt;p&gt;We&#39;re going to store all slots for all pages in the &lt;code&gt;slots&lt;/code&gt; object, which acts as a map-of-maps keyed by the page&#39;s &lt;code&gt;inputPath&lt;/code&gt;. Each page has access to its own slots via Eleventy&#39;s &lt;a href=&quot;https://www.11ty.dev/docs/data-computed/&quot;&gt;Computed Data&lt;/a&gt; feature:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* .eleventy.js */&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; slots &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addGlobalData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;eleventyComputed.slots&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			slots&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slots&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; slots&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2: Register the &lt;code&gt;slot&lt;/code&gt; shortcode&lt;/h3&gt;
&lt;p&gt;To pick up slot values from the Markdown files, we register the &lt;code&gt;slot&lt;/code&gt; paired shortcode.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* .eleventy.js */&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPairedShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;slot&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Missing name for {% slot %} block!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		slots&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After storing the content in the appropriate slot for the page, the shortcode returns an empty string so that the content is not also rendered inline in the Markdown file.&lt;/p&gt;
&lt;p&gt;The content of &lt;code&gt;{% slot &#39;ingredients&#39; %}&lt;/code&gt; can then be used as &lt;code&gt;{{ slots.ingredients }}&lt;/code&gt; anywhere in the HTML layout.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;throw&lt;/code&gt; clause at the beginning guards against a common error. You may expect &lt;code&gt;{% slot ingredients %}&lt;/code&gt; to define a slot named &lt;code&gt;&#39;ingredients&#39;&lt;/code&gt;, but &lt;code&gt;ingredients&lt;/code&gt; in the context of an Eleventy shortcode is &lt;em&gt;an identifier&lt;/em&gt;, and what the shortcode gets as the &lt;code&gt;name&lt;/code&gt; argument is the &lt;em&gt;value&lt;/em&gt; bound to that identifier. The slot name needs to be quoted as &lt;code&gt;{% slot &#39;ingredients&#39; %}&lt;/code&gt;. Checking for an empty &lt;code&gt;name&lt;/code&gt; doesn&#39;t make the shortcode error-proof, but it will cover this frequent typo.&lt;/p&gt;
&lt;h3&gt;Step 3: Use the Eleventy Render plugin&lt;/h3&gt;
&lt;p&gt;The implementation so far assigns pieces of content to their respective slots as plain text. Any Markdown style in slot content will be used literally in the HTML output. To render slots the same way as the rest of the content, we need to use Eleventy&#39;s &lt;a href=&quot;https://www.11ty.dev/docs/plugins/render/&quot;&gt;Render plugin&lt;/a&gt;, which registers a helpful &lt;code&gt;renderTemplate&lt;/code&gt; shortcode.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* .eleventy.js */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; EleventyRenderPlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;@11ty/eleventy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;EleventyRenderPlugin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change the HTML layout to render slot content as Nunjucks + Markdown and you&#39;re good to go:&lt;/p&gt;
&lt;pre class=&quot;language-twig&quot;&gt;&lt;code class=&quot;language-twig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Before: --&gt;&lt;/span&gt; 
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;ingredients&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Ingredients&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ingredients &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- After: --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;ingredients&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Ingredients&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;renderTemplate&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;njk,md&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slots &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; ingredients &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; safe &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;endrenderTemplate&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that by default content inside the &lt;code&gt;renderTemplate&lt;/code&gt; shortcode only has access to the &lt;code&gt;page&lt;/code&gt; and &lt;code&gt;eleventy&lt;/code&gt; objects, so &lt;code&gt;slots&lt;/code&gt; needs to be passed in as &lt;a href=&quot;https://www.11ty.dev/docs/plugins/render/#pass-in-data&quot;&gt;additional data&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Nunjucks, like other templating languages of &lt;a href=&quot;https://jinja.palletsprojects.com/en/3.1.x/&quot;&gt;Jinja&lt;/a&gt; lineage, technically comes with &lt;a href=&quot;https://mozilla.github.io/nunjucks/templating.html#template-inheritance&quot;&gt;template inheritance&lt;/a&gt; via the &lt;code&gt;block&lt;/code&gt; tag. It would make a lot of sense to &lt;a href=&quot;https://github.com/11ty/eleventy/issues/685&quot;&gt;define slotted content with named blocks&lt;/a&gt;. However, Nunjuck&#39;s template inheritance only applies to templates that &lt;code&gt;extend&lt;/code&gt; other templates, and a Markdown file rendered with Nunjucks does not extend its layout file.&lt;/p&gt;
&lt;p&gt;In the absence of the built-in template inheritance, this is the most concise implementation for Markdown slotted content in Eleventy I could &lt;del&gt;come up with&lt;/del&gt; &lt;a href=&quot;https://knowyourmeme.com/memes/i-made-this&quot;&gt;lift wholesale&lt;/a&gt; from &lt;a href=&quot;https://github.com/11ty/eleventy-plugin-bundle&quot;&gt;&lt;code&gt;eleventy-plugin-bundle&lt;/code&gt;&lt;/a&gt;. If it can be further simplified, I&#39;d love to know.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Sass in Eleventy, with versioning</title>
		<link href="https://danburzo.ro/eleventy-sass/" />
		<updated>2023-02-02T00:00:00Z</updated>
		<id>https://danburzo.ro/eleventy-sass/</id>
		<content type="html"
			>&lt;p&gt;There are many approaches to adding &lt;a href=&quot;https://sass-lang.com/&quot;&gt;Sass&lt;/a&gt; support in Eleventy, and several plugins to abstract away these approaches. &lt;a href=&quot;https://www.11ty.dev/docs/plugins/&quot;&gt;The docs page&lt;/a&gt; alone features four separate Sass plugins. When it comes to asset versioning, &lt;em&gt;how&lt;/em&gt; you integrate Sass and Eleventy makes all the difference to the development experience.&lt;/p&gt;
&lt;p&gt;I&#39;ve spent the day tweaking the setup for Eleventy 2.0 to work with content-hashed &lt;code&gt;.scss&lt;/code&gt; files for a new project. Let me walk you through it.&lt;/p&gt;
&lt;h2&gt;Producing content hashes&lt;/h2&gt;
&lt;p&gt;Content hashes are useful as a cache-busting mechanism: whenever the content of the file changes, its corresponding content hash changes as well. For caching purposes, having CSS, JS, images, and other assets include a hash of their content as part of the URL is an excellent idea, and files like &lt;code&gt;style.fc3ff98e.css&lt;/code&gt; are not an uncommon sight across the web.&lt;/p&gt;
&lt;p&gt;Here&#39;s how to produce an 8-character hash for a given string in Node.js:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createHash &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node:crypto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; length &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md5&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;hex&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;getHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// =&gt; &#39;86fb269d&#39;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;getHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hello back!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// =&gt; &#39;7f4b5e8b&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Different string, different hash. Ship it!&lt;/p&gt;
&lt;h2&gt;Adding support for Sass with content hashing in Eleventy&lt;/h2&gt;
&lt;p&gt;The official docs include sample code for &lt;a href=&quot;https://www.11ty.dev/docs/languages/custom/#example-add-sass-support-to-eleventy&quot;&gt;adding support for Sass to Eleventy&lt;/a&gt;, paraphrased below:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTemplateFormats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;outputFileExtension&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;css&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; css &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compileString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; css&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This short snippet sets up a basic workflow to transform all &lt;code&gt;.scss&lt;/code&gt; files from the input directory to &lt;code&gt;.css&lt;/code&gt;, but &lt;a href=&quot;https://github.com/11ty/eleventy/discussions/2786&quot;&gt;there&#39;s a gotcha&lt;/a&gt;. Permalinks for the resulting &lt;code&gt;.css&lt;/code&gt; files are generated &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;compile()&lt;/code&gt; function has chance to run, so we can&#39;t extend it to produce permalinks based on file contents.&lt;/p&gt;
&lt;p&gt;Instead, Sass processing needs to happen earlier in the build process, in the &lt;code&gt;getData()&lt;/code&gt; method. Based on its &lt;code&gt;inputPath&lt;/code&gt;, &lt;code&gt;sass&lt;/code&gt; can read the file directly and populate the template&#39;s &lt;code&gt;data&lt;/code&gt; object. The &lt;code&gt;compile()&lt;/code&gt; and &lt;code&gt;compileOptions.permalink()&lt;/code&gt; methods then simply pick up the bits they&#39;re interested in.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sass&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node:path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/*
		Watch for changes in .scss files.
	 */&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTemplateFormats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;scss&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/*
		Define how to process .scss files.
	 */&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;scss&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/*
			We&#39;re feeding the `inputPath` to Sass directly, so we don&#39;t need Eleventy to read the content of `.scss` files.
		 */&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;/*
			Produce the data for each `.scss` file, including its processed CSS content and its MD5 content hash.
		 */&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;inputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;/*
					Exclude .scss files from `collections.all` so they don&#39;t show up in sitemaps, RSS feeds, etc.
				 */&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;eleventyExcludeFromCollections&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;/*
				Don&#39;t process .scss files that start with an underscore as standalone.
			 */&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; css &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; css&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;css&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;compileOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;/* 
				Disable caching of `.scss` files, for good measure.
			*/&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token function-variable function&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;permalink&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;/*
					Don&#39;t output .scss files that start with an underscore, as per Sass conventions…
				 */&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

				&lt;span class=&quot;token comment&quot;&gt;/*
					…and for other .scss files include the MD5 content hash produced in the `.getData()` method in the output file path.
				 */&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;filePathStem&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_hash&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.css&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/*
			Read the processed CSS content from the data object produced with `.getData()`.
		 */&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_content
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we&#39;ve placed the content hashes in the permalinks of generated &lt;code&gt;.css&lt;/code&gt; files. To retrieve these hashed permalinks inside HTML templates, we prepare an input/output map using &lt;a href=&quot;https://www.11ty.dev/docs/config/#transforms&quot;&gt;a transform&lt;/a&gt;, which helpfully runs through each input file. As a small convenience, we strip the input directory (&lt;code&gt;src&lt;/code&gt; in the snippet below) from the beginning of input paths.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outputMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outputMap&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filepath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	outputMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put &lt;code&gt;outputMap&lt;/code&gt; in a filter to look up versioned URLs for your input files and you&#39;re good to go.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;hashed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filepath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;outputMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;hashed: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;filepath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; not found in map.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; outputMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s how to use the &lt;code&gt;hashed&lt;/code&gt; filter in a template to obtain the versioned URL for &lt;code&gt;src/_assets/style.scss&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-twig&quot;&gt;&lt;code class=&quot;language-twig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- This… --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;text/css&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;_assets/style.scss&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; hashed &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- …turns to this --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;text/css&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
	&lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;_assets/style.c8ad33ff.css&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This article has discussed one way of adding Sass support to Eleventy 2.0 that lets you include content hashes into the resulting CSS file paths. It&#39;s short enough to plop it straight into your &lt;code&gt;.eleventy.js&lt;/code&gt; config:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Full listing: add support for Sass with content hashing in Eleventy&lt;/summary&gt;
&lt;p&gt;Here&#39;s the full &lt;strong&gt;.eleventy.js&lt;/strong&gt; configuration file:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sass&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node:path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createHash &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node:crypto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
	For the given `content` string, 
	generate an MD5 hash of `length` chars.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; length &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md5&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;hex&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/*
		Watch for changes in .scss files.
	 */&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTemplateFormats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;scss&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/*
		Define how to process .scss files.
	 */&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;scss&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/*
			We&#39;re feeding the `inputPath` to Sass directly, so we don&#39;t need Eleventy to read the content of `.scss` files.
		 */&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;/*
			Produce the data for each `.scss` file, including its processed CSS content and its MD5 content hash.
		 */&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;inputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;/*
				Don&#39;t process .scss files that start with an underscore as standalone.
			 */&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; css &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;/*
					Exclude .scss files from `collections.all` so they don&#39;t show up in sitemaps, RSS feeds, etc.
				 */&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;eleventyExcludeFromCollections&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;_content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; css&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;_hash&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;css&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;compileOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;/* 
				Disable caching of `.scss` files, for good measure.
			*/&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token function-variable function&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;permalink&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;/*
					Don&#39;t output .scss files that start with an underscore, as per Sass conventions…
				 */&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

				&lt;span class=&quot;token comment&quot;&gt;/*
					…and for other .scss files include the MD5 content hash produced in the `.getData()` method in the output file path.
				 */&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;filePathStem&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_hash&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.css&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/*
			Read the processed CSS content from the data object produced with `.getData()`.
		 */&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_content
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outputMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outputMap&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filepath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		outputMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;hashed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filepath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;outputMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;hashed: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;filepath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; not found in map.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; outputMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;markdownTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;dataTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;htmlTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dist&#39;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;If you prefer the plugin route, it looks like &lt;a href=&quot;https://github.com/kentaroi/eleventy-sass/&quot;&gt;eleventy-sass&lt;/a&gt; and its companion &lt;a href=&quot;https://github.com/kentaroi/eleventy-plugin-rev&quot;&gt;eleventy-plugin-rev&lt;/a&gt; do a similar job.&lt;/p&gt;
&lt;h2 id=&quot;addenda&quot;&gt;Addenda&lt;/h2&gt;
&lt;p&gt;A few hours after first publishing this article, &lt;a href=&quot;https://github.com/11ty/eleventy-plugin-bundle&quot;&gt;@11ty/eleventy-plugin-bundle&lt;/a&gt; was released to help you &lt;q&gt;create minimal per-page or app-level bundles of CSS, JavaScript, or HTML to be included in your Eleventy project&lt;/q&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update March 1, 2023:&lt;/strong&gt; &lt;code&gt;.scss&lt;/code&gt; partials (i.e. files starting with an underscore) should not be processed at all in &lt;code&gt;getData()&lt;/code&gt;. They&#39;ll be processed when they&#39;re included in regular &lt;code&gt;.scss&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update August 9, 2023:&lt;/strong&gt; &lt;code&gt;.scss&lt;/code&gt; partials should likewise not be included in collections, so they don’t show up in RSS, sitemaps, etc.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Line-height tricks made simpler with the ‘lh’ CSS unit</title>
		<link href="https://danburzo.ro/line-height-lh/" />
		<updated>2023-01-13T00:00:00Z</updated>
		<id>https://danburzo.ro/line-height-lh/</id>
		<content type="html"
			>&lt;p&gt;With Safari Technology Preview having supported it for a while, and Chromium 109 enabling user-facing support in a first batch of browsers, it&#39;s time to see a few ways in which the &lt;code&gt;lh&lt;/code&gt; CSS unit is a useful addition.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;lh&lt;/code&gt; unit is formally described as follows:&lt;/p&gt;
&lt;figure&gt;
&lt;blockquote&gt;
&lt;p&gt;Equal to the computed value of the line-height property of the element on which it is used, converting &lt;code&gt;normal&lt;/code&gt; to an absolute length by using only the metrics of the first available font.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;When used in the value of the &lt;code&gt;font-size&lt;/code&gt; property on the element they refer to, the local font-relative lengths resolve against the computed metrics of the parent element — or against the computed metrics corresponding to the initial values of the &lt;code&gt;font&lt;/code&gt; and &lt;code&gt;line-height&lt;/code&gt; properties, if the element has no parent. Likewise, when &lt;code&gt;lh&lt;/code&gt; or &lt;code&gt;rlh&lt;/code&gt; units are used in the value of the &lt;code&gt;line-height&lt;/code&gt; property on the element they refer to, they resolve against the computed &lt;code&gt;line-height&lt;/code&gt; and &lt;code&gt;font&lt;/code&gt; metrics of the parent element — or the computed metrics corresponding to the initial values of the &lt;code&gt;font&lt;/code&gt; and &lt;code&gt;line-height&lt;/code&gt; properties, if the element has no parent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figcaption&gt;
&lt;p&gt;Description of the &lt;code&gt;lh&lt;/code&gt; unit in the &lt;em&gt;6.1.1. Font-relative Lengths&lt;/em&gt; section of &lt;cite&gt;&lt;a href=&quot;https://drafts.csswg.org/css-values-4/#font-relative-lengths&quot;&gt;CSS Values and Units Module Level 4&lt;/a&gt; (Editor&#39;s Draft)&lt;/cite&gt;.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;More concisely, &lt;code&gt;1lh&lt;/code&gt; is the computed line height of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the parent when used in &lt;code&gt;font-size&lt;/code&gt; or &lt;code&gt;line-height&lt;/code&gt; declarations, or&lt;/li&gt;
&lt;li&gt;of the element itself in any other declaration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Uses for the &lt;code&gt;lh&lt;/code&gt; unit&lt;/h2&gt;
&lt;div class=&quot;lh-support-notice&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The CSS examples below rely on support for the &lt;code&gt;lh&lt;/code&gt; unit, which your current browser does not provide. To keep the snippets clearer, I have not included any style fallbacks, so the examples won&#39;t look great. Load this article in Safari TP, Chrome 109+, or Edge 109+ to see the effects in action.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;h3&gt;Ruled paper effect&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;lh&lt;/code&gt; unit makes it easy to produce CSS images that are in step with the lines of text. A straightforward example of that is drawing horizontal rules each &lt;code&gt;1lh&lt;/code&gt;, which we achieve here with &lt;code&gt;repeating-linear-gradient()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.index-card&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		transparent 0 &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1lh - 1px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
		midnightblue &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1lh - 1px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1lh
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/*
		Aligning the background image to the content-box
		lets us use any padding on the element.
	 */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;index-card&quot; lang=&quot;ro&quot;&gt;
Emanuel urcă scara întunecoasă. Mirosea în aer a produse farmaceutice și a cauciuc ars. În fundul coridorului îngust, recunoscu ușa albă care îi fusese indicată. Intră fără să mai bată.
&lt;/blockquote&gt;
&lt;p&gt;This was not impossible to do before. When &lt;code&gt;line-height&lt;/code&gt; is a unitless multiplier of the font&#39;s size, we can produce a custom CSS property &lt;code&gt;var(--lh)&lt;/code&gt; with the same &lt;code&gt;1lh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;--line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;--lh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1em * &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This basic technique can be used for a variety of effects, such as Adam Argyle&#39;s &lt;a href=&quot;https://codepen.io/argyleink/pen/QWrzKOg&quot;&gt;text of a different color on each line&lt;/a&gt;. It&#39;s also a good addition to the &lt;a href=&quot;https://danburzo.ro/css-layout-debugger/&quot;&gt;debugging toolbox&lt;/a&gt;. Throughout the article, I&#39;ll use the &lt;code&gt;.index-card&lt;/code&gt; CSS class on examples to make the line boxes visible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In Safari TP, zooming in and out of the page affects the computed value of &lt;code&gt;1lh&lt;/code&gt; inside CSS gradients, causing the ruled paper to shift out of alignment with the text [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=252075&quot;&gt;WebKit#252075&lt;/a&gt;].&lt;/p&gt;
&lt;h3&gt;Nicely sized inline icons&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;lh&lt;/code&gt; unit is useful for harmonizing sizes with the surrounding text and, in particular, latching onto the line box in a predictable way. This usage is described by Šime Vidas in 2020 (via &lt;a href=&quot;https://css-tricks.com/lh-and-rlh-units/&quot;&gt;CSS Tricks&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.with-icon:before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; midnightblue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inline-block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;vertical-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bottom&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;margin-inline-end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.25em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;index-card&quot; lang=&quot;ro&quot;&gt;
	&lt;span class=&quot;with-icon&quot;&gt;O ușă se deschise&lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here, a pseudo-element of &lt;code&gt;height: 1lh&lt;/code&gt; is anchored to either end of the line box using &lt;code&gt;vertical-align: top&lt;/code&gt; or &lt;code&gt;bottom&lt;/code&gt; to ensure perfect alignment.&lt;/p&gt;
&lt;p&gt;Speaking of perfect, I should note that &lt;code&gt;1lh&lt;/code&gt; refers to the &lt;em&gt;ideal line height&lt;/em&gt; of an element, but the actual line-height can get bumped by things like inline blocks or &lt;code&gt;&amp;lt;sup&amp;gt;&lt;/code&gt; elements:&lt;/p&gt;
&lt;blockquote class=&quot;index-card&quot; lang=&quot;ro&quot;&gt;
	&lt;span class=&quot;with-icon&quot;&gt;O ușă&lt;sup&gt;1&lt;/sup&gt; se deschise&lt;/span&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Baseline grid one-liner&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer / note to future self&lt;/strong&gt;: A &lt;code&gt;line-height: &amp;lt;length&amp;gt;;&lt;/code&gt; declaration already inherits just fine without any additional work. Having re-read it, this section needs to be rethought to make a better point, best to ignore it before I have the chance to do that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I saved the best for last.&lt;/p&gt;
&lt;p&gt;Beyond a simple abstraction over &lt;code&gt;calc(1em * var(--line-height))&lt;/code&gt;, elements can be made to inherit their parent&#39;s line-height. Maintaining vertical rhythm for the entire document is reduced to an elegant declaration:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.6&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;html *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How cool is that? I mean sure, there&#39;s more to vertical rhythm than an uniform line height. It involves block sizes, along with properties that affect the box model (border, padding, margin). These are currently brewing in the &lt;a href=&quot;https://w3c.github.io/csswg-drafts/css-rhythm&quot;&gt;CSS Rhythmic Sizing&lt;/a&gt; specification, including a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/line-height-step&quot;&gt;&lt;code&gt;line-height-step&lt;/code&gt;&lt;/a&gt; property that&#39;s already available in Chromium under a run-time feature flag.&lt;/p&gt;
&lt;p&gt;The implications of going document-wide for this need to be explored before we can declare we have another &lt;code&gt;* { box-sizing: border-box }&lt;/code&gt;-style gold nugget on our hands. In the meantime, it works pretty well applied locally:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.baseline-grid&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.3&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.baseline-grid *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;margin-block&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;text-indent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1lh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.baseline-grid .smaller&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.8em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;index-card baseline-grid&quot; lang=&quot;ro&quot;&gt;
&lt;p&gt;Încăperea în care se găsi părea mai veche încă și mai mucegăită decât coridorul. Lumina venea printr’un singur geam și răspândea o claritate albastră și nesigură peste desordinea din salonaș, unde revistele zăceau răvășite pretutindeni, acoperind masa de marmură și scaunele solemne, învelite în halate albe ca în niște haine comode de voiaj, înainte de mutare.&lt;/p&gt;
&lt;p class=&quot;smaller&quot;&gt;Emanuel mai mult căzu decât se așeză într’un fotoliu. Observă cu surprindere umbre parcurgând odaia și descoperi subit că geamul din fund era în realitate un aquarium în care pluteau lent pești negri, bulbucați și grași. Câteva secunde rămase cu ochii mari deschiși urmărindu-le alunecarea leneșă, uitând aproape pentru ce venise.&lt;/p&gt;
&lt;p&gt;Într’adevăr pentru ce venise aici? Aha! Își aduse aminte și tuși încetișor pentru a-și anunța prezența, dar nimeni nu răspunse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this setup, &lt;code&gt;1lh&lt;/code&gt; becomes a fixed measure, like &lt;code&gt;1rem&lt;/code&gt;, that can be used to keep in sync margins, indents, and others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In Safari TP, imposing a minimum font size via the browser setting affects &lt;code&gt;line-height: 1lh&lt;/code&gt; [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=252108&quot;&gt;WebKit#252108&lt;/a&gt;].&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;These have been the three examples I could come up with, or find, in an evening. I hope these can serve as a starting point for finding new, more interesting uses for the &lt;code&gt;lh&lt;/code&gt; unit.&lt;/p&gt;
&lt;p&gt;The text fragments are from M. Blecher&#39;s &lt;em&gt;Inimi cicatrizate&lt;/em&gt; (&lt;em&gt;Scarred hearts&lt;/em&gt;), available &lt;a href=&quot;https://llll.ro/max-blecher/inimi-cicatrizate/&quot;&gt;in the public domain&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Why do mobile browsers share canonical URLs?</title>
		<link href="https://danburzo.ro/canonical-sharing/" />
		<updated>2023-01-10T00:00:00Z</updated>
		<id>https://danburzo.ro/canonical-sharing/</id>
		<content type="html"
			>&lt;p&gt;It did it again the other day.&lt;/p&gt;
&lt;p&gt;I had tucked some research leads from Google Books — a website that lets you perform searches in the content of digitized books, many very old — into Safari&#39;s Reading List on my phone, for later reference. A while later, when it came to following the breadcrumbs I had left for myself back to the insight they were meant to help germinate, lo and behold — all links to Google Books were missing my search queries. What had I found in these now unfamiliar books?&lt;/p&gt;
&lt;p&gt;Since it was not the first occurrence of the sort, I&#39;ve had a low-key suspicion for a while that when you share or bookmark a URL in iOS Safari, it takes into account the page&#39;s &lt;em&gt;canonical URL&lt;/em&gt; whenever it finds one. I&#39;ve been content to just shrug off the occasional mishap and move on, but this time I took the opportunity to dig into the subject.&lt;/p&gt;
&lt;h2&gt;Some background on canonical URLs&lt;/h2&gt;
&lt;p&gt;The canonical URL is meant to convey, as per the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/links.html#link-type-canonical&quot;&gt;HTML spec&lt;/a&gt;, the &lt;q&gt;preferred URL for the current document&lt;/q&gt;. The introductory paragraphs from &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc6596&quot;&gt;RFC6596: The Canonical Link Relation&lt;/a&gt; tell us most of what we need to know about its intent:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The canonical link relation specifies the preferred IRI from resources with duplicative content. Common implementations of the canonical link relation are to specify the preferred version of an IRI from duplicate pages created with the addition of IRI parameters (e.g., session IDs) or to specify the single-page version as preferred over the same content separated on multiple component pages.&lt;/p&gt;
&lt;p&gt;In regard to the link relation type, &amp;quot;canonical&amp;quot; can be described informally as the author&#39;s preferred version of a resource. More formally, the canonical link relation specifies the preferred IRI from a set of resources that return the context IRI&#39;s content in duplicated form. Once specified, applications such as search engines can focus processing on the canonical, and references to the context (referring) IRI can be updated to reference the target (canonical) IRI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A canonical URL can be defined either through &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link&quot;&gt;a &lt;code&gt;Link&lt;/code&gt; HTTP header&lt;/a&gt; or, more commonly, via a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; HTML element:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Welcome to my website&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;canonical&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;https://danburzo.ro/&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How did browsers end up using canonical URLs?&lt;/h2&gt;
&lt;p&gt;As described in the introduction to RFC6596, one major use case for canonical URLs is to help search engines make sense of several URLs that point to the same underlying content. This is also the angle Google uses to present &lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls#why-it-matters&quot;&gt;the benefits of canonical URLs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sometime around 2017, this mechanism meant for machines was co-opted by browsers to address a very different problem: nudging users &lt;a href=&quot;https://news.ycombinator.com/item?id=15085159&quot;&gt;away from Google&#39;s AMP Viewer&lt;/a&gt;, and towards the original web pages it was caching. Safari 11 for iOS was soon followed by &lt;a href=&quot;https://chromereleases.googleblog.com/2018/01/chrome-for-android-update.html&quot;&gt;Chrome 64 for Android&lt;/a&gt; in favoring a page&#39;s canonical URL for certain interactions, such as sharing and bookmarking.&lt;/p&gt;
&lt;p&gt;This change worked pretty well for that singular purpose: most article page proxied by the AMP Viewer could be unambiguously traced back to their original URL.&lt;/p&gt;
&lt;p&gt;However, this was released as a general mechanism that affects any page that uses &lt;code&gt;&amp;lt;link rel=canonical&amp;gt;&lt;/code&gt;. That&#39;s &lt;em&gt;a lot&lt;/em&gt; of pages, more than half of the pages analyzed by HTTP Archive for this year&#39;s &lt;a href=&quot;https://almanac.httparchive.org/en/2022/seo#canonical-tags&quot;&gt;Web Almanac&lt;/a&gt;, including crowd favorite Wikipedia.org.&lt;/p&gt;
&lt;p&gt;In the general case, the results are more of a mixed bag.&lt;/p&gt;
&lt;h3&gt;The pros and cons of sharing canonical URLs&lt;/h3&gt;
&lt;p&gt;Some effects are decidedly positive: various pieces of user tracking gunk are stripped from URLs before they&#39;re passed to friends, as these are generally not featured in the page&#39;s canonical URL. By design or chance, among the things removed from URLs is the odd personally-identifying piece of information, so you could build the case that the feature helps protect the user&#39;s privacy.&lt;/p&gt;
&lt;p&gt;But things that are beneficial to a search engine don&#39;t always match user needs. In fact, they can even clash.&lt;/p&gt;
&lt;p&gt;Take filters on an e-commerce website as an example. For search engines, it&#39;s useful to know that various filtering criteria and sorting options, most often reflected as query parameters in the URL, reflect the same underlying content. It makes sense, from an SEO perspective, to lump all combinations together under a single canonical URL.&lt;/p&gt;
&lt;p&gt;For a user, on the other hand, the specific filtering criteria and sorting options are kind of the whole point, aren&#39;t they? When I browse my favorite bookstore&#39;s website for &lt;samp&gt;foreign books, in English, available in stock, sorted by most recent first&lt;/samp&gt; and I commit that URL to my bookmarks for quick access, my intent is for the URL to be preserved at that exact level of specificity.&lt;/p&gt;
&lt;p&gt;Even if you do manage to devise a pattern that fulfills both user needs and SEO goals, you&#39;re not yet out of the woods. When using canonical URLs &lt;em&gt;in any form&lt;/em&gt; on your website, you&#39;re implicitly signing up for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;remaining vigilant about updating the &lt;code&gt;&amp;lt;link rel=canonical&amp;gt;&lt;/code&gt; element in response to all operations that may alter the URL, such as with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/History&quot;&gt;History API&lt;/a&gt; via &lt;code&gt;pushState()&lt;/code&gt; or &lt;code&gt;replaceState()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;depending on the browser implementation, &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=250317&quot;&gt;waving goodbye to using fragment identifiers&lt;/a&gt;. Since it&#39;s exclusive to the client, the URL fragment can&#39;t be reflected by the server in the canonical URL. It will, therefore, be stripped when sharing or bookmarking the page — unless the browser is proactive in keeping it, or the fragment is kept in sync on the canonical link with client-side JavaScript.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Speaking of browser implementations, let&#39;s see how they stack up at the moment of writing.&lt;/p&gt;
&lt;h2&gt;Current browser behavior&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I&#39;m using &lt;a href=&quot;https://danburzo.ro/demos/canonical-link.html&quot;&gt;this demo page&lt;/a&gt; to test browser behavior, where the &lt;em&gt;original URL&lt;/em&gt; is, depending on the things you click on, of the form &lt;code&gt;https://danburzo.ro/&lt;wbr /&gt;demos/&lt;wbr /&gt;&lt;mark class=&quot;wavy&quot;&gt;canonical-link.html&lt;wbr /&gt;?hello=world#a&lt;/mark&gt;&lt;/code&gt;. Its &lt;em&gt;canonical URL&lt;/em&gt; is defined as &lt;code&gt;https://danburzo.ro/&lt;wbr /&gt;demos/&lt;wbr /&gt;&lt;mark class=&quot;wavy&quot;&gt;canonical-link-canonical.html&lt;/mark&gt;&lt;/code&gt; via the &lt;code&gt;&amp;lt;link rel=canonical&amp;gt;&lt;/code&gt; element. Notice the different HTML file name and lack of query string and fragment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Desktop browsers seem to be doing a good job of using the original URL when sharing and bookmarking a web page. Across the major mobile platforms and browsers, the situation is more diverse.&lt;/p&gt;
&lt;p&gt;On Android:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Samsung Internet 19&lt;/strong&gt; and &lt;strong&gt;Firefox Android 108&lt;/strong&gt; share and bookmark the original, intact URL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chrome Android 108&lt;/strong&gt; bookmarks the original URL. When sharing the web page, it uses the canonical URL, but picks up the fragment identifier from the original URL. This is an enhancement introduced around 2021 [&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1038187&quot;&gt;Chromium#1038187&lt;/a&gt;] that makes sharing canonical URLs a little less problematic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge Android 108&lt;/strong&gt;: no data, as I couldn&#39;t find an &lt;code&gt;.apk&lt;/code&gt; to load on the device (test data appreciated!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On iOS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Firefox iOS 108&lt;/strong&gt; shares and bookmarks the original URL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safari iOS 16.2&lt;/strong&gt; uses the canonical URL for both sharing and bookmarking but loses the fragment identifier from the original URL [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=250317&quot;&gt;WebKit#250317&lt;/a&gt;], including the &lt;a href=&quot;https://github.com/GoogleChromeLabs/link-to-text-fragment&quot;&gt;text fragment links&lt;/a&gt; for which Safari 16.1 has recently &lt;a href=&quot;https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes&quot;&gt;added support&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chrome iOS 108&lt;/strong&gt;, in an effort to match Safari [&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1323782&quot;&gt;Chromium#1323782&lt;/a&gt;], works a bit worse than its Android counterpart: it shares the canonical URL, but without copying over the fragment identifier from the original URL. When bookmarking, the original URL is used. (&lt;em&gt;Add to Reading List&lt;/em&gt; is weird: it&#39;s made to match Safari&#39;s bookmarking behavior since it&#39;s accessed from the same sharing panel, even though the page gets added to Chrome&#39;s own Reading List.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge iOS 108&lt;/strong&gt;, like Safari and Chrome, shares the canonical URL without copying over the fragment identifier from the original URL. When bookmarking, the original URL is used.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;font-size: 0.9em&quot;&gt;
	&lt;caption align=&quot;top&quot;&gt;A summary of mobile browser behavior in regards to sharing and bookmarking URLs in the presence of a canonical link, January 2023.&lt;/caption&gt;
	&lt;thead&gt;
		&lt;th colspan=&quot;2&quot;&gt;Browser&lt;/th&gt;
		&lt;th&gt;Shared URL&lt;/th&gt;
		&lt;th&gt;Bookmarked URL&lt;/th&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;th rowspan=&quot;4&quot;&gt;Android&lt;/th&gt;
			&lt;th&gt;Firefox&lt;/th&gt;
			&lt;td&gt;Original&lt;/td&gt;
			&lt;td&gt;Original&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;Samsung Internet&lt;/th&gt;
			&lt;td&gt;Original&lt;/td&gt;
			&lt;td&gt;Original&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;Chrome&lt;/th&gt;
			&lt;td&gt;Canonical, fragment kept&lt;/td&gt;
			&lt;td&gt;Original&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;Edge&lt;/th&gt;
			&lt;td&gt;&lt;i&gt;Missing data&lt;/i&gt;&lt;/td&gt;
			&lt;td&gt;&lt;i&gt;Missing data&lt;/i&gt;&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th rowspan=&quot;4&quot;&gt;iOS&lt;/th&gt;
			&lt;th&gt;Firefox&lt;/th&gt;
			&lt;td&gt;Original&lt;/td&gt;
			&lt;td&gt;Original&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;Safari&lt;/th&gt;
			&lt;td&gt;Canonical, fragment lost&lt;/td&gt;
			&lt;td&gt;Canonical, fragment lost&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;Chrome&lt;/th&gt;
			&lt;td&gt;Canonical, fragment lost&lt;/td&gt;
			&lt;td&gt;Depends, see notes&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;Edge&lt;/th&gt;
			&lt;td&gt;Canonical, fragment lost&lt;/td&gt;
			&lt;td&gt;Original&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;How do browser vendors feel about the status quo?&lt;/p&gt;
&lt;p&gt;Chrome seems to get a steady stream of issue reports about the &amp;quot;wrong URL&amp;quot; being shared or copied to the clipboard — see, for example, &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=799955&quot;&gt;Chromium#799955&lt;/a&gt;, &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=924309&quot;&gt;Chromium#924309&lt;/a&gt; — to which the resolution is invariably &lt;em&gt;works as intended&lt;/em&gt; along with a note that the team is reconsidering its approach to using canonical URLs in light of its tradeoffs. The conversation is framed more around erroneous usage of &lt;code&gt;&amp;lt;link rel=canonical&amp;gt;&lt;/code&gt; by authors — see conversations in &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=988497&quot;&gt;Chromium#988497&lt;/a&gt;, &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1202789&quot;&gt;Chromium#1202789&lt;/a&gt;, &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1306663&quot;&gt;Chromium#1306663&lt;/a&gt; — than on any fundamental flaw or limitation with the approach itself, but it&#39;s a start.&lt;/p&gt;
&lt;p&gt;Over at Mozilla arguments for, or against, using the canonical URL for sharing [&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1794879&quot;&gt;Mozilla#1794879&lt;/a&gt;] and bookmarking [&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=502418&quot;&gt;Mozilla#502418&lt;/a&gt;] have not yet truly taken off.&lt;/p&gt;
&lt;h2&gt;Possible solutions&lt;/h2&gt;
&lt;p&gt;I don&#39;t have much in the way of alternative solutions, but I tend to err on the side of user agency.&lt;/p&gt;
&lt;p&gt;For each browser on each mobile platform, the sharing UI is flexible enough to accommodate a persistent user preference. Pictured below, Safari 16.2&#39;s sharing panel already has an &lt;samp&gt;Options&lt;/samp&gt; section that could very well let you tweak how a link gets shared or bookmarked.&lt;/p&gt;
&lt;figure&gt;
&lt;img loading=&quot;lazy&quot; src=&quot;https://danburzo.ro/img/canonical-sharing/safari-ios-sharing-url.png&quot; alt=&quot;The iOS Safari sharing panel floats above the web page content. In the panel&#39;s header, underneath the page title and domain name, a button labeled Options.&quot; width=&quot;562&quot; height=&quot;633&quot; /&gt;
&lt;figcaption&gt;
&lt;p&gt;The sharing panel in Safari 16.2.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Finally, an invitation: I&#39;ve tried to find and link all browser issues relevant to the topic of user-facing canonical URLs. If you have an opinion or a piece of anecdata, either in support or against their usage, please share it with browser vendors.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to Šime for the useful feedback.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Miscellaneous bits and bobs&lt;/h2&gt;
&lt;p&gt;In the process of testing browser behaviors, I also logged issues for a couple of things I noticed in macOS Safari: that a page whose defined canonical URL differs from its actual URL will never be marked as visited [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=250319&quot;&gt;WebKit#250319&lt;/a&gt;], and that clicking an in-page link to a text fragment does not update the URL fragment [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=250320&quot;&gt;WebKit#250320&lt;/a&gt;].&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Get useful input values with formDataMap()</title>
		<link href="https://danburzo.ro/formdatamap/" />
		<updated>2023-01-08T00:00:00Z</updated>
		<id>https://danburzo.ro/formdatamap/</id>
		<content type="html"
			>&lt;p&gt;Chris Ferdinandi&#39;s recent article for &lt;em&gt;12 Days of Web&lt;/em&gt; called &lt;a href=&quot;https://12daysofweb.dev/2022/formdata-api/&quot;&gt;FormData API&lt;/a&gt; reminded me about a helper function I wrote to quickly wire up plain HTML form controls in interactive demos of the &lt;em&gt;move slider make thing happen&lt;/em&gt; type, without needing to reach for &lt;a href=&quot;https://github.com/dataarts/dat.gui&quot;&gt;dat.gui&lt;/a&gt; or a similar library.&lt;/p&gt;
&lt;p&gt;When your set of controls is organized as a plain HTML form, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData&quot;&gt;the &lt;code&gt;FormData&lt;/code&gt; DOM interface&lt;/a&gt; is a useful way to read the values. It also interacts nicely with other APIs, such as &lt;code&gt;fetch()&lt;/code&gt; and &lt;code&gt;URLSearchParams&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For our specific use case, it does present a couple of inconveniences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you read its entries via a dedicated API, with two separate methods &lt;code&gt;.get()&lt;/code&gt; and &lt;code&gt;.getAll()&lt;/code&gt; depending on whether you&#39;re expecting one value or many;&lt;/li&gt;
&lt;li&gt;everything except &lt;code&gt;Blob&lt;/code&gt;s is cast to a string.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s take these minuscule concerns as an opportunity to implement a slightly-enhanced version of &lt;code&gt;FormData&lt;/code&gt; of our own, that produces a plain old JavaScript object, and keeps the value types closer to their underlying form controls.&lt;/p&gt;
&lt;h2&gt;Let&#39;s reimplement &lt;code&gt;FormData&lt;/code&gt; for some reason&lt;/h2&gt;
&lt;p&gt;The HTML specification helpfully &lt;a href=&quot;https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set&quot;&gt;lists the steps&lt;/a&gt; for constructing the entry list for a given form element. We want our implementation to be fairly robust against the markup you&#39;d write today, but not necessarily cover the historical aspects; as such, the implementation is going to gloss over &lt;code&gt;&amp;lt;input type=&#39;image&#39;&amp;gt;&lt;/code&gt; with its &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input/image#using_the_x_and_y_data_points&quot;&gt;very specific behavior&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Finding elements that should be submitted&lt;/h3&gt;
&lt;p&gt;Form elements are grouped in overlapping categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Submittable elements&lt;/em&gt; are &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;, and any form-associated custom elements.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Listed elements&lt;/em&gt; include &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt;, in addition to submittable elements. They can have an explicit &lt;code&gt;form&lt;/code&gt; attribute that associates them with a form somewhere else in the DOM tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements&quot;&gt;&lt;code&gt;HTMLFormElement.elements&lt;/code&gt;&lt;/a&gt; collection does the legwork of gathering the &lt;em&gt;listed elements&lt;/em&gt; associated with the form, taking into account any explicit &lt;code&gt;form&lt;/code&gt; attribute on the elements. The only work that leaves for our implementation is to filter out any element types that are &lt;em&gt;listed but not submittable&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;formDataMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; submitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; excludedTags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FIELDSET&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OBJECT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OUTPUT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; submittable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;elements&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;excludedTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Out of all submittable elements, only form controls that comply with a set of rules are actually submitted. Any such element must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have a non-empty &lt;code&gt;name&lt;/code&gt; attribute (except for custom elements associated with the form, which &lt;a href=&quot;https://danburzo.ro/formdatamap/#handling-custom-elements&quot;&gt;in some cases can go without&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;not be disabled, either via its &lt;code&gt;disabled&lt;/code&gt; attribute or through its position in the DOM tree;&lt;/li&gt;
&lt;li&gt;not be nested inside a &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; element;&lt;/li&gt;
&lt;li&gt;be checked, in the case of &lt;code&gt;radio&lt;/code&gt; and &lt;code&gt;checkbox&lt;/code&gt; inputs;&lt;/li&gt;
&lt;li&gt;not be a button, except for the button that submitted the form, if applicable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These rules are merged into the &lt;code&gt;shouldSubmit(el)&lt;/code&gt; function below:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;formDataMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; submitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; excludedTags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FIELDSET&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OBJECT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OUTPUT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; excludedTypes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;reset&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shouldSubmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;excludedTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;excludedTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;submit&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; el &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; submitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;radio&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;checkbox&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;:disabled&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;closest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;datalist&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; toSubmit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;elements&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shouldSubmit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Adding values based on the type of form control&lt;/h3&gt;
&lt;p&gt;With the rules out of the way, on to the fun part of getting nice values based on the type of each element. Here&#39;s the plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;String&lt;/code&gt;&lt;/strong&gt;: most form controls work okay with a string value. For inputs of type &lt;code&gt;checkbox&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;hidden&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;radio&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, or &lt;code&gt;text&lt;/code&gt;, as well as for &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; elements, the plain &lt;code&gt;element.value&lt;/code&gt; suffices. Similarly, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; elements produce strings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Number&lt;/code&gt;&lt;/strong&gt; for inputs of types &lt;code&gt;number&lt;/code&gt; and &lt;code&gt;range&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Date&lt;/code&gt;&lt;/strong&gt; for inputs of type &lt;code&gt;date&lt;/code&gt;, &lt;code&gt;datetime-local&lt;/code&gt;; date-adjacent types such as &lt;code&gt;month&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, and &lt;code&gt;week&lt;/code&gt; are left as strings currently, but they can probably afford something more interesting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;File&lt;/code&gt;&lt;/strong&gt; for inputs of type &lt;code&gt;file&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;URL&lt;/code&gt;&lt;/strong&gt; for inputs of type &lt;code&gt;url&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Putting everything all together, here&#39;s the final function:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;formDataMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; submitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; excludedTags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FIELDSET&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OBJECT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OUTPUT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; excludedTypes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;reset&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shouldSubmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;excludedTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;excludedTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;submit&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; el &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; submitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;radio&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;checkbox&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;:disabled&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;closest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;datalist&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; val&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		result&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
			Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasOwn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; 
				&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; val&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
				&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; val&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;elements&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldSubmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;number&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;range&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;date&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;datetime-local&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueAsDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;url&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;select-one&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;select-multiple&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;selectedOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
				&lt;span class=&quot;token parameter&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; option&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Let&#39;s use &lt;code&gt;formDataMap()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;One great web platform feature with which to pair &lt;code&gt;formDataMap()&lt;/code&gt; is event propagation, which enables us to capture events on the ancestor form:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;song-config&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		Song title:
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		Cowbell level: 
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;cowbell&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;range&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;11&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Apply configuration&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; form &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;song-config&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;formDataMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* do great things with `data` */&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the level of responsiveness the interactive demo needs, you can choose between &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;change&lt;/code&gt; and &lt;code&gt;submit&lt;/code&gt; events. The latter also gives you access to the form&#39;s &lt;code&gt;submitter&lt;/code&gt; element, which can be factored into the returned data, a feature FormData does not currently support [&lt;a href=&quot;https://github.com/whatwg/xhr/issues/262&quot;&gt;whatwg/xhr#262&lt;/a&gt;]:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;submit&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;formDataMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;submitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/* do great things with `data` */&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Update March 11, 2023:&lt;/em&gt; The aforementioned issue has been fixed, with &lt;code&gt;submitter&lt;/code&gt; added as a second, optional argument to the &lt;code&gt;FormData()&lt;/code&gt; constructor. &lt;a href=&quot;https://developer.chrome.com/en/blog/chrome-112-beta/&quot;&gt;Chrome 112&lt;/a&gt; is the first browser shipping support for it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In all fairness, &lt;small&gt;[leans in and starts whispering:]&lt;/small&gt; instead of reimplementing everything from scratch, you could get most of the same functionality with the form&#39;s vanilla &lt;code&gt;FormData&lt;/code&gt; object &lt;a href=&quot;https://vanillajstoolkit.com/helpers/serialize/&quot;&gt;serialized to a plain JavaScript object&lt;/a&gt;, followed by casting the values to numbers, dates, etc. as needed before using them for computations.&lt;/p&gt;
&lt;p&gt;However, for that extra bit of convenience, &lt;code&gt;formDataMap()&lt;/code&gt; is listed in full &lt;a href=&quot;https://danburzo.ro/snippets/formdatamap/&quot;&gt;on its separate page&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;handling-custom-elements&quot;&gt;
	Appendix: Handling custom HTML elements
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals&quot;&gt;ElementInternals&lt;/a&gt; is a new API that lets custom HTML elements participate in forms. The article &lt;a href=&quot;https://web.dev/more-capable-form-controls/&quot;&gt;More capable form controls&lt;/a&gt; by Arthur Evans goes into more detail, but in a nutshell, your custom element needs to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;make itself form-associated by declaring the static &lt;code&gt;formAssociated&lt;/code&gt; property;&lt;/li&gt;
&lt;li&gt;access form functionality with the &lt;code&gt;ElementInternals.attachInternals()&lt;/code&gt; method.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyControl&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; formAssociated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attachInternals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Set the element&#39;s submission value&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFormValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;some-value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

customElements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-control&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; MyControl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;setFormValue()&lt;/code&gt; method, custom elements can specify their &lt;em&gt;submission value&lt;/em&gt; which the &lt;code&gt;FormData&lt;/code&gt; API can access. The submission value can be a string, a &lt;code&gt;Blob&lt;/code&gt;, or a &lt;code&gt;FormData&lt;/code&gt; object. The latter is used when the custom element wants to relay multiple values, and it&#39;s the only case when a custom element doesn&#39;t need a &lt;code&gt;name&lt;/code&gt; attribute for it to be included in the form&#39;s submission data.&lt;/p&gt;
&lt;p&gt;Unfortunately for us, the value set with the &lt;code&gt;setFormValue()&lt;/code&gt; method is not accessible through any standard interface, so it&#39;s up to each custom element to decide how (and if) to expose an equivalent.&lt;/p&gt;
&lt;p&gt;To remain generic, &lt;code&gt;formDataMap()&lt;/code&gt; can only fall back to the standard &lt;code&gt;append(name, el.value)&lt;/code&gt; approach.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2022</title>
		<link href="https://danburzo.ro/favorite-records-2022/" />
		<updated>2022-12-05T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2022/</id>
		<content type="html"
			>&lt;p&gt;December, the month of dwindling daylight and obligatory retrospection, is upon us once again. Here are my favorite records of the year 2022, ordered alphabetically. My top ten are marked with a ★ (star).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://aldousharding.bandcamp.com/album/warm-chris&quot;&gt;Aldous Harding — Warm Chris&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://alkcm.bandcamp.com/album/oxy-music&quot;&gt;Alex Cameron — Oxy Music&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://angelolsen.bandcamp.com/album/big-time&quot;&gt;Angel Olsen — Big Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://annacalvi.bandcamp.com/album/tommy&quot;&gt;Anna Calvi — Tommy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.arcadefire.com/&quot;&gt;Arcade Fire — We&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arcticmonkeys.com/&quot;&gt;Arctic Monkeys — The Car&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/solanales-balmorhea-12643&quot;&gt;Balmorhea — Solanales&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://beachhouse.bandcamp.com/album/once-twice-melody&quot;&gt;Beach House — Once Twice Melody&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://bethorton.bandcamp.com/album/weather-alive&quot;&gt;Beth Orton — Weather Alive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://biosphere.bandcamp.com/album/shortwave-memories&quot;&gt;Biosphere — Shortwave Memories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://bobbyoroza.bandcamp.com/album/get-on-the-otherside&quot;&gt;Boby Oroza (feat. Cold Diamond &amp;amp; Mink) — Get on the Otherside&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.brian-eno.net/&quot;&gt;Brian Eno — &lt;span class=&quot;sc&quot;&gt;FOREVERANDEVERNOMORE&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://carmband.bandcamp.com/album/carm-ii&quot;&gt;CARM — CARM II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danielavery.bandcamp.com/album/ultra-truth&quot;&gt;Daniel Avery — Ultra Truth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caribouband.bandcamp.com/album/cherry&quot;&gt;Daphni — Cherry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;☞ &lt;a href=&quot;https://daniellanois.lnk.to/playerpiano&quot;&gt;Daniel Lanois — Player, Piano&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://florenceandthemachine.net/&quot;&gt;Florence + the Machine — Dance Fever&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://florist.bandcamp.com/album/florist&quot;&gt;Florist — Florist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://haai.bandcamp.com/album/baby-we-re-ascending&quot;&gt;HAAi — Baby, We&#39;re Ascending&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hagop.bandcamp.com/album/bolts&quot;&gt;Hagop Tchaparian — Bolts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jeanmicheljarre.com/&quot;&gt;Jean-Michel Jarre — Oxymore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/drone-mass-johannsson-12620&quot;&gt;Jóhann Jóhannsson, Theatre of Voices, Paul Hillier, &lt;span class=&quot;sc&quot;&gt;ACME&lt;/span&gt; — Jóhannsson: Drone Mass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kaitlynaureliasmith.bandcamp.com/album/i-could-be-your-dog-i-could-be-your-moon&quot;&gt;Kaitlyn Aurelia Smith &amp;amp; Emile Mosseri — I Could Be Your Dog / I Could Be Your Moon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://kalimalone.bandcamp.com/album/living-torch&quot;&gt;Kali Malone — Living Torch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kramer.bandcamp.com/album/music-for-films-edited-by-moths&quot;&gt;Kramer — Music For Films Edited By Moths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/us/album/sunset/1619436551&quot;&gt;Library Tapes — Sunset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://orcd.co/exaudia&quot;&gt;Lisa Gerrard &amp;amp; Marcello De Francisci — Exaudia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://madrugada.no/&quot;&gt;Madrugada — Chimes at Midnight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://orcd.co/matthiasburden&quot;&gt;Matthias Gusset — Burden&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://metronomy.bandcamp.com/album/small-world&quot;&gt;Metronomy — Small World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/music-for-animals&quot;&gt;Nils Frahm — Music for Animals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nosajthing.bandcamp.com/album/continua&quot;&gt;Nosaj Thing — Continua&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://orenambarchi.bandcamp.com/album/ghosted&quot;&gt;Oren Ambarchi / Johan Berthling / Andreas Werliin — Ghosted&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://perfumegenius.bandcamp.com/album/ugly-season&quot;&gt;Perfume Genius — Ugly Season&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rogereno.com/&quot;&gt;Roger Eno — The Turning Year&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://motomami.rosalia.com/&quot;&gt;Rosalía — Motomami&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://sarahdavachi.bandcamp.com/album/two-sisters&quot;&gt;Sarah Davachi — Two Sisters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sevdaliza.bandcamp.com/album/raving-dahlia&quot;&gt;Sevdaliza — Raving Dahlia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://sharonvanetten.bandcamp.com/album/weve-been-going-about-this-all-wrong-deluxe-edition&quot;&gt;Sharon Van Etten — We&#39;ve Been Going About This All Wrong (Deluxe Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sudanarchives.bandcamp.com/album/natural-brown-prom-queen&quot;&gt;Sudan Archives — Natural Brown Prom Queen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sunssignature.bandcamp.com/album/suns-signature&quot;&gt;Sun&#39;s Signature — Sun&#39;s Signature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thesmile.bandcamp.com/album/a-light-for-attracting-attention&quot;&gt;The Smile — A Light for Attracting Attention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tindersticks.bandcamp.com/album/stars-at-noon-original-soundtrack&quot;&gt;Tindersticks — Stars at Noon (Original Soundtrack)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vanessa-wagner.bandcamp.com/album/mirrored&quot;&gt;Vanessa Wagner — Mirrored&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://vanessa-wagner.bandcamp.com/album/study-of-the-invisible&quot;&gt;Vanessa Wagner — Study of the Invisible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://milan-records.myshopify.com/products/preorder-a-tribute-to-ryuichi-sakamoto-to-the-moon-and-back-2x-lp&quot;&gt;Various Artists — A Tribute to Ryuichi Sakamoto: To the Moon and Back&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://whomadewho.dk/releases&quot;&gt;WhoMadeWho — &lt;span class=&quot;sc&quot;&gt;UUUU&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://williambasinski.bandcamp.com/album/on-reflection&quot;&gt;William Basinski &amp;amp; Janek Schaefer — “...On Reflection”&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yeahyeahyeahs.bandcamp.com/album/cool-it-down&quot;&gt;Yeah Yeah Yeahs — Cool It Down&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The manicule ☞ marks retroactive additions of favorite albums I’ve discovered after having made the initial list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live shows&lt;/strong&gt; I&#39;ve attended this year: Tindersticks in Lisbon; Dead Can Dance and Arctic Monkeys in Bucharest; Altın Gün and WhoMadeWho in Cluj.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other 2022 lists: &lt;a href=&quot;https://bleep.com/top-10-albums-of-the-year-2022&quot;&gt;Bleep&lt;/a&gt;, &lt;a href=&quot;https://www.roughtrade.com/gb/collection/albums-of-the-year-2022&quot;&gt;Rough Trade UK&lt;/a&gt;, &lt;a href=&quot;https://thequietus.com/articles/32400-the-quietus-top-100-albums-of-2022-norman-records&quot;&gt;The Quietus&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Relational data in Eleventy</title>
		<link href="https://danburzo.ro/eleventy-relational-data/" />
		<updated>2022-11-26T00:00:00Z</updated>
		<id>https://danburzo.ro/eleventy-relational-data/</id>
		<content type="html"
			>&lt;p&gt;Many-to-many relationships are a fixture of structured content.&lt;/p&gt;
&lt;p&gt;A basic example is a collection of &lt;samp&gt;Posts&lt;/samp&gt;, each written by one or more &lt;samp&gt;Authors&lt;/samp&gt;. So how do you shape the content so that it&#39;s easy to generate pages for &lt;samp&gt;Posts&lt;/samp&gt; complete with nice bylines, and for individual &lt;samp&gt;Authors&lt;/samp&gt; including a list of their posts?&lt;/p&gt;
&lt;h2&gt;The setup&lt;/h2&gt;
&lt;p&gt;The code snippets throughout this article use Eleventy 1.0.2, the latest version at the time of writing. They assume the input directory is a folder named &lt;code&gt;content/&lt;/code&gt;, and use the Nunjucks templating language throughout. The corresponding &lt;code&gt;.eleventy.js&lt;/code&gt; configuration is below:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* File: .eleventy.js */&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;markdownTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;htmlTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;content/&lt;/code&gt; folder holds all &lt;samp&gt;Posts&lt;/samp&gt; and &lt;samp&gt;Authors&lt;/samp&gt; in individual &lt;code&gt;.md&lt;/code&gt; files. We&#39;d like the frontmatter of one such file to reference information stored in a different file, and have that information readily available when rendering the page.&lt;/p&gt;
&lt;p&gt;&lt;samp&gt;Posts&lt;/samp&gt; reference each &lt;samp&gt;Author&lt;/samp&gt; by their &lt;em&gt;file path relative to the input directory&lt;/em&gt;. This path serves as an unique key for a piece of content. Here&#39;s how &lt;code&gt;content/posts/hello.md&lt;/code&gt; might look:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;title: Hello
authors:
  - authors/dan.md
  - authors/catalin.md
layout: layouts/post.njk&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

This is our first collective post!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;samp&gt;Author&lt;/samp&gt; files then hold the actual information in the frontmatter — things such as the full name, or the path to an avatar image — as well as the author bio in the Markdown content section. Here&#39;s &lt;code&gt;content/authors/dan.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;title: Dan Burzo
avatar: img/authors/dan-burzo.jpg
layout: layouts/author.html&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

This is Dan&#39;s short bio.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to accomplish two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;expand the &lt;code&gt;authors&lt;/code&gt; array in the &lt;samp&gt;Post&lt;/samp&gt;&#39;s frontmatter into the actual pages they reference, so we can access all their properties in the template.&lt;/li&gt;
&lt;li&gt;on the &lt;samp&gt;Author&lt;/samp&gt; page, aggreggate the author&#39;s posts under the &lt;code&gt;author_posts&lt;/code&gt; data field.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Augment the frontmatter with Computed Data&lt;/h2&gt;
&lt;p&gt;Eleventy comes with a cool feature called &lt;a href=&quot;https://www.11ty.dev/docs/data-computed/&quot;&gt;Computed Data&lt;/a&gt; that lets us add template data, at any level of the data cascade. More importantly, it lets us &lt;em&gt;overwrite&lt;/em&gt; existing frontmatter data with richer values. We only need to define &lt;code&gt;eleventyComputed&lt;/code&gt; once for each type of content, using &lt;a href=&quot;https://www.11ty.dev/docs/data-template-dir/&quot;&gt;directory data files&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For &lt;samp&gt;Posts&lt;/samp&gt;, the content of the &lt;code&gt;content/posts/posts.11tydata.js&lt;/code&gt; file is shown below. By defining the &lt;code&gt;tags&lt;/code&gt; field, we add all entries in the folder to the &lt;code&gt;posts&lt;/code&gt; collection. We do the same in &lt;code&gt;content/authors/authors.11tydata.js&lt;/code&gt; to gather all &lt;samp&gt;Authors&lt;/samp&gt; in the corresponding collection.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In Eleventy &lt;a href=&quot;https://www.11ty.dev/docs/collections/&quot;&gt;Collections&lt;/a&gt;, the unique identifier for a piece of content is its &lt;code&gt;inputPath&lt;/code&gt;, which is the items&#39;s file path relative to the project&#39;s root, starting with the path for the input directory. In our case, all &lt;code&gt;inputPath&lt;/code&gt;s start with &lt;code&gt;./content/&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With &lt;code&gt;eleventyComputed&lt;/code&gt;, we replace the &lt;samp&gt;Post&lt;/samp&gt;&#39;s &lt;code&gt;authors&lt;/code&gt; field with the corresponding items from the &lt;code&gt;authors&lt;/code&gt; collection, matching their &lt;code&gt;inputPath&lt;/code&gt;. Remember that we referenced post authors by paths that were relative to the input directory. We have to concatenate that back into our paths before we can match against &lt;code&gt;inputPath&lt;/code&gt; values.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* content/posts/posts.11tydata.js */&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;layouts/post.njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;posts&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;eleventyComputed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postAuthors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authors &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authors&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; postAuthors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;authorPath&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
				collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
					item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./content/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;authorPath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The inverse, showing all &lt;samp&gt;Posts&lt;/samp&gt; by a specific &lt;samp&gt;Author&lt;/samp&gt;, is similarly done with &lt;code&gt;eleventyComputed&lt;/code&gt;. This time, we look in the &lt;code&gt;posts&lt;/code&gt; collection for items whose &lt;code&gt;authors&lt;/code&gt; data field includes the current &lt;code&gt;inputPath&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* content/authors/authors.11tydata.js */&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;layouts/author.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;authors&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;eleventyComputed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;author_posts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inputPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postAuthors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authors &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; postAuthors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
					&lt;span class=&quot;token parameter&quot;&gt;authorPath&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./content/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;authorPath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; inputPath
				&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup makes it straightforward to use the enriched template data. In the layout for &lt;samp&gt;Posts&lt;/samp&gt;...&lt;/p&gt;
&lt;pre class=&quot;language-twig&quot;&gt;&lt;code class=&quot;language-twig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- content/_includes/layouts/post.njk --&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hgroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; title &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; By
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;for&lt;/span&gt; author &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; authors &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; author&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; author&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%-&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; loop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;last &lt;span class=&quot;token delimiter punctuation&quot;&gt;-%}&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%-&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;-%}&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;hgroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; safe &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...and for &lt;samp&gt;Authors&lt;/samp&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-twig&quot;&gt;&lt;code class=&quot;language-twig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- content/_includes/layouts/author.html --&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; title &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; safe &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; title &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&#39;s posts&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;figure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;for&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; author_posts &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token twig language-twig&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag-name keyword&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two basic patterns — &lt;em&gt;find by ID&lt;/em&gt; and &lt;em&gt;filter by attribute&lt;/em&gt; — can be replicated for each relationship between data types you care to represent.&lt;/p&gt;
&lt;p&gt;But before we pepper that pesky &lt;code&gt;&amp;quot;./content/&amp;quot;&lt;/code&gt; string around a dozen directory data files, let&#39;s factor it out, while improving performance in the process.&lt;/p&gt;
&lt;p&gt;To make collection items easier to find by ID, let&#39;s put all the content in one big dictionary, indexed by the keys used across the &lt;code&gt;.md&lt;/code&gt; files to reference each other: the file path relative to the input directory. In other words, the &lt;code&gt;inputPath&lt;/code&gt; with the input directory trimmed. We do that by defining a &lt;a href=&quot;https://www.11ty.dev/docs/collections/#advanced-custom-filtering-and-sorting&quot;&gt;custom collection&lt;/a&gt; called &lt;code&gt;_indexed&lt;/code&gt; in the project&#39;s &lt;code&gt;.eleventy.js&lt;/code&gt; config:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* .eleventy.js */&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_DIR&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/*
		Index all content relative to the input directory
		in an Eleventy collection called `collections._indexed`
	 */&lt;/span&gt;
	config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_indexed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_DIR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			index&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;markdownTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;htmlTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_DIR&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This custom collection simplifies our computed data. To fetch &lt;samp&gt;Post&lt;/samp&gt; authors, we can look them up in the index directly, which is much faster than iterating through the collection, and involves none of that string concatenation.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* content/posts/posts.11tydata.js */&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;layouts/post.njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;posts&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;eleventyComputed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postAuthors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authors &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_indexed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; postAuthors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;authorPath&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;authorPath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the case of &lt;samp&gt;Author&lt;/samp&gt; posts, we replace string concatenation with reading the paths from the index, making the code if not faster then at least more resilient.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* content/authors/authors.11tydata.js */&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;layouts/author.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;authors&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;eleventyComputed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function-variable function&quot;&gt;author_posts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inputPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_indexed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postAuthors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authors &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; postAuthors
					&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;authorPath&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;authorPath&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These could be further refactored into reusable functions, to make declaring many relationships more palatable, but this is the basic idea.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>This website uses a variable font</title>
		<link href="https://danburzo.ro/variable-fonts/" />
		<updated>2022-08-22T00:00:00Z</updated>
		<id>https://danburzo.ro/variable-fonts/</id>
		<content type="html"
			>&lt;p&gt;Around the end of 2021, this little corner of the web started using a variable font. &lt;a href=&quot;https://www.productiontype.com/family/newsreader&quot;&gt;Newsreader&lt;/a&gt; is a beautiful design from Production Type commissioned by Google Fonts, whose sources are &lt;a href=&quot;https://github.com/productiontype/NewsReader&quot;&gt;available on GitHub&lt;/a&gt; under the Open Font license (OFL).&lt;/p&gt;
&lt;p&gt;I&#39;d been reading about variable fonts for a while, but had &lt;em&gt;probably&lt;/em&gt; never actually typed up the CSS with my fingers for a project up to that point. Here are a few things I learned about self-hosting variable fonts in the process of freshening up the typography on here.&lt;/p&gt;
&lt;h2&gt;A first good thing to do is add all the necessary properties to the &lt;code&gt;@font-face&lt;/code&gt; declaration&lt;/h2&gt;
&lt;p&gt;Newsreader comes with two separate font files, one for roman and one for italics. Each file has two variation axes: the weight (&lt;code&gt;wght&lt;/code&gt;) and the optical size (&lt;code&gt;opsz&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;My initial &lt;code&gt;@font-face&lt;/code&gt; declaration for the roman style was pretty straightforward, and replacing the old typeface worked well out of the box.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@font-face&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Newsreader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; normal&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string url&quot;&gt;&#39;/fonts/Newsreader.woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;woff2-variations&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Newsreader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It became apparent rather soon, however, that this setup only works in Firefox. Things that were supposed to look bold — headings, &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt;, and the like — didn&#39;t look as good in Safari and Chrome, as seen in &lt;a href=&quot;https://danburzo.ro/demos/variable-fonts/declarations.html&quot;&gt;this demo&lt;/a&gt;. Both had trouble, in different ways, rendering &lt;code&gt;font-weight: 700&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
	&lt;a href=&quot;https://danburzo.ro/img/font-face-font-weight.png&quot;&gt;&lt;img src=&quot;https://danburzo.ro/img/font-face-font-weight.png&quot; alt=&quot;A grid showing the rendering of font weight 400 and 700 across Firefox, Safari and Chrome. Firefox looks as expected, while Safari produces an exaggerated bold, and Chrome looks almost like the normal weight.&quot; width=&quot;406&quot; height=&quot;211&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;
&lt;p&gt;Each browser renders &lt;code&gt;font-weight: 700&lt;/code&gt; differently.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Safari produces what seems to be twice-bolded text by obtaining  the appropriate bold weight from the variable font but then synthesizing a bolded version of that. In fact, disabling the browser&#39;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/font-synthesis&quot;&gt;font synthesis&lt;/a&gt; with &lt;code&gt;font-synthesis: none&lt;/code&gt; fixes the issue. As for Chrome, whatever it&#39;s doing, it&#39;s not improved by this declaration.&lt;/p&gt;
&lt;p&gt;What gives? &lt;code&gt;wght&lt;/code&gt; was supposed to be a registered variation axis that can be controlled via &lt;code&gt;font-weight&lt;/code&gt; but, as seen in the demo, only &lt;code&gt;font-variant-settings: &amp;quot;wght&amp;quot; 700&amp;quot;&lt;/code&gt; works consistently across browsers for bold text.&lt;/p&gt;
&lt;p&gt;It was only obvious in retrospect that &lt;strong&gt;a webfont with a weight variation axis absolutely needs its weight range spelled out in the &lt;code&gt;@font-face&lt;/code&gt; declaration&lt;/strong&gt;, for the axis to be correctly mapped to the &lt;code&gt;font-weight&lt;/code&gt; property. You do that with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight&quot;&gt;&lt;code&gt;@font-face/font-weight&lt;/code&gt;&lt;/a&gt; property:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@font-face&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Newsreader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; normal&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 200 800&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string url&quot;&gt;&#39;/fonts/Newsreader.woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;woff2-variations&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar declarations are needed for other registered axes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;slnt&lt;/code&gt; axis which controls the angle of the oblique style needs a range defined with &lt;code&gt;@font-face/font-style&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;wdth&lt;/code&gt; axis which controls the font width needs a range defined with &lt;code&gt;@font-face/font-stretch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding the correct &lt;code&gt;@font-face&lt;/code&gt; properties enables declarations such as &lt;code&gt;font-style: oblique 30deg&lt;/code&gt; or &lt;code&gt;font-width: 125%&lt;/code&gt; to work as expected.&lt;/p&gt;
&lt;p&gt;Variable fonts can also &lt;em&gt;technically&lt;/em&gt; include the registered &lt;code&gt;ital&lt;/code&gt; axis to bundle both roman and italic styles in a single file. Using such a font in CSS is &lt;a href=&quot;https://rwt.io/typography-tips/getting-bent-current-state-italics-variable-font-support&quot;&gt;a bit more complicated&lt;/a&gt;, so as a consequence pretty much everyone bundles romans and italics in separate files.&lt;/p&gt;
&lt;h3&gt;A note on &lt;code&gt;format()&lt;/code&gt; in the &lt;code&gt;src&lt;/code&gt; descriptor&lt;/h3&gt;
&lt;p&gt;By the time I got to reading up on variable fonts, the syntax for &lt;code&gt;@font-face/src&lt;/code&gt; &lt;a href=&quot;https://drafts.csswg.org/css-fonts/#src-desc&quot;&gt;in the spec&lt;/a&gt; did no longer match the recommendations I&#39;d found elsewhere for hinting to the browser that the &lt;code&gt;url()&lt;/code&gt; points to a variable font.&lt;/p&gt;
&lt;p&gt;Where were &lt;code&gt;format(&#39;woff2-variations&#39;)&lt;/code&gt; and &lt;code&gt;format(&#39;woff2 supports variations&#39;)&lt;/code&gt; coming from? But, more importantly, how to usefully hint variable fonts today? I made &lt;a href=&quot;https://danburzo.ro/demos/variable-fonts/font-face-src.html&quot;&gt;a browser test&lt;/a&gt; to check support for the various syntaxes and came to these conclusions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;format(&#39;*-variations&#39;)&lt;/code&gt; was the proposed syntax around the time when browsers adopted variable fonts, and the only syntax supported by browsers at the time of writing;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format(woff2) tech(variations)&lt;/code&gt; is the latest syntax. It separates the &lt;em&gt;font format&lt;/em&gt; from &lt;em&gt;font technology descriptor&lt;/em&gt;, as &lt;a href=&quot;https://github.com/w3c/csswg-drafts/blob/main/css-fonts-4/src-explainer.md&quot;&gt;explained&lt;/a&gt; by Chris Lilley and Dominik Röttsches. This syntax is unsupported in current browsers and, if specified, must be done in a second &lt;code&gt;@font-face/src&lt;/code&gt; declaration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these findings in mind (which you can now find in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#browser_compatibility&quot;&gt;compatibility table on MDN&lt;/a&gt;), variable fonts using the WOFF2 format only really need the &lt;code&gt;format(&#39;woff2-variations&#39;)&lt;/code&gt; hint to ensure only browsers that have support for variations download the font file. This syntax works today and will most likely work indefinitely.&lt;/p&gt;
&lt;h2&gt;It&#39;s also probably a good idea to optimize variable web fonts (as long as you&#39;re allowed to)&lt;/h2&gt;
&lt;p&gt;Between the two variation axes (weight and optical size), Newsreader packs the information from nine separate source fonts. That adds up to a total of 455kb for the two WOFF2 files it ships. This is the overhead you have to pay for being able to produce as many fonts as you like, everywhere across the design-variation space.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src=&quot;https://danburzo.ro/img/variable-fonts/newsreader-vf-axes.svg&quot; width=&quot;340&quot; /&gt;
	&lt;figcaption&gt;Newsreader is based on nine master fonts, here distributed horizontally along the &lt;em&gt;Weight&lt;/em&gt; axis (&lt;code&gt;wght&lt;/code&gt;) and vertically along the &lt;em&gt;Optical size&lt;/em&gt; (&lt;code&gt;opsz&lt;/code&gt;) axis.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This overhead is not always justified for web pages, so a common thing to do is split the font into many pieces across an aspect of the font. &lt;a href=&quot;https://fonttools.readthedocs.io/en/latest/index.html&quot;&gt;&lt;code&gt;fontTools&lt;/code&gt;&lt;/a&gt; is a collection of libraries and command-line tools for all sorts of font manipulation written in Python.&lt;/p&gt;
&lt;p&gt;Richard Rutter has a guide on installing &lt;code&gt;fonttools&lt;/code&gt; on macOS and using it to &lt;a href=&quot;https://clagnut.com/blog/2418/&quot;&gt;subset variable fonts&lt;/a&gt;. One understanding of &lt;em&gt;subsetting&lt;/em&gt; is to cherry-pick a set of Unicode ranges that cover all the glyphs your content needs and call it a day. A more resilient approach is to effectively &lt;em&gt;split&lt;/em&gt; the font in a series of scripts („alphabets”). As long as you know the Unicode bounds of these scripts — and Paul Hebert reminds us that, in a pinch, you can &lt;a href=&quot;https://cloudfour.com/thinks/font-subsetting-strategies-content-based-vs-alphabetical/#implementing-alphabet-based-subsets&quot;&gt;grab some ranges from Google Fonts&lt;/a&gt; — this lets you serve the entire glyph set the font offers, and let the browser fetch whatever it needs to render the content of the web page.&lt;/p&gt;
&lt;h3&gt;Narrowing the variation space&lt;/h3&gt;
&lt;p&gt;In addition to glyph sets, there&#39;s another direction along which to subset a variable font. &lt;code&gt;fonttools&lt;/code&gt; comes with &lt;a href=&quot;https://fonttools.readthedocs.io/en/latest/varLib/instancer.html&quot;&gt;the &lt;code&gt;varLib.instancer&lt;/code&gt; module&lt;/a&gt; that allows you &lt;q&gt;to create full instances (i.e. static fonts) from variable fonts, as well as &#39;partial&#39; variable fonts that only contain a subset of the original variation space.&lt;/q&gt;&lt;/p&gt;
&lt;p&gt;There is such a thing as &lt;em&gt;variable lite&lt;/em&gt;. If, for example, you can make do with a single optical size, or you&#39;ve decided &lt;code&gt;wght: 376&lt;/code&gt; is the perfect weight for body text, and you only need another bold weight to go with it, it might be worth it to either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;produce a handful of static instances of the variable font, or&lt;/li&gt;
&lt;li&gt;restrict the design-variation space to just the region you need.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command below pins the optical size at 12 while reducing the weight range to &lt;code&gt;[300–700]&lt;/code&gt; for the Newsreader roman font:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;fonttools varLib.instancer ./Newsreader.woff2 &lt;span class=&quot;token assign-left variable&quot;&gt;wght&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;:700 &lt;span class=&quot;token assign-left variable&quot;&gt;opsz&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; ./Newsreader-partial.woff2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This cuts the file size in half, from 215kb to 98kb. I&#39;ve intentionally picked a &amp;quot;bad&amp;quot; example, because the reduction is foremost attributable to removing the optical size variation axis. If we omit the &lt;code&gt;wght=300:700&lt;/code&gt; option in our command we end up with mostly the same reduction (215kb to 102kb):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;fonttools varLib.instancer ./Newsreader.woff2 &lt;span class=&quot;token assign-left variable&quot;&gt;opsz&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; ./Newsreader-partial.woff2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recall that Newsreader uses font data from &lt;code&gt;wght=200,400,800&lt;/code&gt;, corresponding to the minimum, default, and maximum weight. To render the &lt;code&gt;wght=300:700&lt;/code&gt; range we still need data for the new minimum and maximum weight. To truly see a reduction in size, our weight range would have to be defined in such a way that at least one set of font data can be dropped from the variable font, for example &lt;code&gt;wght=400:800&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;fonttools varLib.instancer ./Newsreader.woff2 &lt;span class=&quot;token assign-left variable&quot;&gt;wght&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;:800 &lt;span class=&quot;token assign-left variable&quot;&gt;opsz&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; ./Newsreader-partial.woff2&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Legal aspects of subsetting&lt;/h3&gt;
&lt;p&gt;Fonts are distributed under a variety of licenses that govern their use. The following apply to fonts distributed under the &lt;a href=&quot;https://en.wikipedia.org/wiki/SIL_Open_Font_License&quot;&gt;SIL Open Font License&lt;/a&gt;, as is the case for Newsreader. Obviously, I&#39;m not a lawyer so this is just my layperson understanding of the matter.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Converting a &lt;code&gt;.ttf&lt;/code&gt; / &lt;code&gt;.otf&lt;/code&gt; file to a &lt;code&gt;.woff&lt;/code&gt; / &lt;code&gt;.woff2&lt;/code&gt; just to optimize its delivery across the network (WOFF is a compressed format), without any other alterations, preserves its Functional Equivalence, and is permitted under the OFL license;&lt;/li&gt;
&lt;li&gt;Subsetting a font alters its functionality, so it produces a derivative work, which is subject to some rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When producing a derivative work you need to change the font&#39;s name to something completely different &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;if&lt;/em&gt; the OFL license mentions a Reserved Font Name (RFN) as part of its copyright line. Newsreader has the following copyright line:&lt;/p&gt;
&lt;figure&gt;
&lt;blockquote&gt;
&lt;p&gt;Copyright 2020 The Newsreader Project Authors (http://github.com/&lt;wbr /&gt;productiontype/&lt;wbr /&gt;Newsreader)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figcaption&gt;A sample copyright line from the OFL license that does not reserve the font name.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the authors wanted &lt;em&gt;Newsreader&lt;/em&gt; to be an RFN they would have mentioned that in the copyright line, and I would then have been compelled by the license to rename my subsets some cringeworthy pun like, I dunno, &lt;em&gt;Oldswriter&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;blockquote&gt;
&lt;p&gt;Copyright 2020 The Newsreader Project Authors (http://github.com/&lt;wbr /&gt;productiontype/&lt;wbr /&gt;Newsreader), with Reserved Font Name Newsreader&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figcaption&gt;A sample copyright line from the OFL license that reserves the font name.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Anyways, just something to be aware of. And if I&#39;m getting this wrong, I would appreciate a nudge!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Addenda: readings, tools, and resources&lt;/h2&gt;
&lt;p&gt;I can&#39;t help myself from turning every article into a little compendium, so here are some tangentials I&#39;ve found useful.&lt;/p&gt;
&lt;p&gt;OpenType Font Variations is part of the &lt;a href=&quot;https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview&quot;&gt;OpenType specification&lt;/a&gt;. John Hudson announced this addition to OpenType 1.8 with &lt;a href=&quot;https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369&quot;&gt;Introducing OpenType Variable Fonts&lt;/a&gt;, highly recommended reading.&lt;/p&gt;
&lt;p&gt;A few guides to getting started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide&quot;&gt;Variable fonts guide&lt;/a&gt; on MDN&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://variablefonts.io/&quot;&gt;A Variable Fonts Primer&lt;/a&gt;. Still from Jason Pamental, &lt;a href=&quot;https://rwt.io/typography-tips/variable-fonts-what-web-authors-need-know&quot;&gt;Variable Fonts: What Authors Need to Know&lt;/a&gt; and &lt;a href=&quot;https://24ways.org/2019/an-introduction-to-variable-fonts/&quot;&gt;An Introduction to Variable Fonts&lt;/a&gt; for 24 Ways&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/variable-fonts/&quot;&gt;Introduction to variable fonts on the web&lt;/a&gt; on web.dev.&lt;/li&gt;
&lt;li&gt;The newly-inaugurated &lt;a href=&quot;https://fonts.google.com/knowledge&quot;&gt;Google Fonts Knowledge&lt;/a&gt; covers variable fonts.&lt;/li&gt;
&lt;li&gt;Zach Leatherman on developing a &lt;a href=&quot;https://www.zachleat.com/web/css-tricks-web-fonts/&quot;&gt;strategy for loading fonts on CSS tricks&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An excellent tool for inspecting variable fonts (and, really, any kind of webfont) is Wakamai Fondue, available &lt;a href=&quot;https://wakamaifondue.com/&quot;&gt;on the web&lt;/a&gt; and &lt;a href=&quot;https://pixelambacht.nl/2021/wakamai-fondue-command-line/&quot;&gt;at the command line&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, a couple of fun facts that surfaced while researching this article:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chris Coyier: &lt;a href=&quot;https://chriscoyier.net/2022/08/02/actually-the-san-francisco-typeface-does-ship-as-a-variable-font/&quot;&gt;Actually, the San Francisco Typeface Does Ship as a Variable Font&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Firefox is the only browser that uses &lt;code&gt;b, strong { font-weight: bolder }&lt;/code&gt; in its user agent stylesheet. Šime Vidas &lt;a href=&quot;https://css-tricks.com/firefoxs-bolder-default-is-a-problem-for-variable-fonts/&quot;&gt;points out&lt;/a&gt; how this can be a problem with variable fonts.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/52203351/why-is-unicode-restricted-to-0x10ffff&quot;&gt;the largest Unicode code point&lt;/a&gt; is &lt;code&gt;U+10FFFF&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Sizing images based on their aspect ratio</title>
		<link href="https://danburzo.ro/aspect-ratio-size/" />
		<updated>2022-08-07T00:00:00Z</updated>
		<id>https://danburzo.ro/aspect-ratio-size/</id>
		<content type="html"
			>&lt;p&gt;Grids of logos are my nightmare.&lt;/p&gt;
&lt;p&gt;I mean, never mind the opportunity to learn the bewildering, but admittedly finite, array of file formats in which visual assets can conceivably be stored and distributed. But the intricate dance of making all logos look equally prominent in the grid is something for which I can only muster very little enthusiasm.&lt;/p&gt;
&lt;p&gt;Like with all tedious things, I&#39;ve found myself wishing more than once that CSS included a declarative way to make images of various aspect ratios look the same size.&lt;/p&gt;
&lt;h2&gt;Making images „look the same size”&lt;/h2&gt;
&lt;p&gt;A definition of that might be to make the images occupy the &lt;em&gt;same surface area&lt;/em&gt;. Let&#39;s work out the math.&lt;/p&gt;
&lt;p&gt;We want to keep constant the area of an image of natural width &lt;code&gt;W&lt;/code&gt; and height &lt;code&gt;H&lt;/code&gt;. To that end, we must find its &lt;em&gt;preferred dimensions&lt;/em&gt; &lt;code&gt;Wp&lt;/code&gt; and &lt;code&gt;Hp&lt;/code&gt; so that &lt;code&gt;Wp × Hp&lt;/code&gt; is uniform across images. The givens:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;area &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Wp &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Hp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
aspect_ratio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;W&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;H&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Wp &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; Hp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…ultimately lead to the formula below. (Individual steps are left as an exercise to the reader.)&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Wp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;area&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;W&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s express this formula in HTML and CSS.&lt;/p&gt;
&lt;p&gt;You should already have explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes on &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements in your markup to define the intrinsic size of the image and &lt;a href=&quot;https://jakearchibald.com/2022/img-aspect-ratio/&quot;&gt;prevent layout shifts&lt;/a&gt; as the image loads. The values of these attributes cannot be used in CSS directly, so we also pass them as custom properties via the &lt;code&gt;style&lt;/code&gt; attribute.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;ratio-gallery&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; 
		&lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;https://source.unsplash.com/random/800x600&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
		&lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;800 by 600 pixels placeholder image&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;800&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
		&lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;600&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; 
		&lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;--w&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 800&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;--h&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we&#39;re passing the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; as unitless numbers. This gives us the most flexibility when using the values in CSS math functions. The &lt;a href=&quot;https://drafts.csswg.org/css-values-4/#exponent-funcs&quot;&gt;&lt;code&gt;sqrt()&lt;/code&gt;&lt;/a&gt; function, in particular, only accepts &lt;code&gt;&amp;lt;number&amp;gt;&lt;/code&gt;s. Our basic CSS looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.ratio-gallery&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--basis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 300px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.ratio-gallery img&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--w&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--h&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--basis&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; / &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--h&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of defining an &lt;code&gt;--area&lt;/code&gt; constant like in our math formula, we use its square root directly as &lt;code&gt;--basis&lt;/code&gt;. This has the added benefit of being more intuitive: you can think of its value as making the image as large as a square of that side length. For example, &lt;code&gt;--basis: 300px&lt;/code&gt; makes the image as large as a &lt;code&gt;300×300&lt;/code&gt; square.&lt;/p&gt;
&lt;p&gt;That&#39;s pretty much all there is to the technique. Below are some images sized with it, placed in a flexbox container. In a browser that supports the &lt;code&gt;sqrt()&lt;/code&gt; CSS function — which, at the moment of writing, is just Safari — you should see three images of identical surface areas:&lt;/p&gt;
&lt;figure&gt;
&lt;ul class=&quot;ratio-gallery&quot;&gt;
	&lt;li&gt;
		&lt;img src=&quot;https://danburzo.ro/img/aspect-ratio-size/500x500.png&quot; alt=&quot;500 by 500 pixels placeholder image&quot; width=&quot;500&quot; height=&quot;500&quot; style=&quot;--w: 500; --h: 500&quot; /&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;img src=&quot;https://danburzo.ro/img/aspect-ratio-size/800x400.png&quot; alt=&quot;800 by 400 pixels placeholder image&quot; width=&quot;800&quot; height=&quot;400&quot; style=&quot;--w: 800; --h: 400&quot; /&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;img src=&quot;https://danburzo.ro/img/aspect-ratio-size/400x600.png&quot; alt=&quot;400 by 600 pixels placeholder image&quot; width=&quot;400&quot; height=&quot;600&quot; style=&quot;--w: 400; --h: 600&quot; /&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;/figure&gt;
&lt;h2&gt;Fallback to a fixed aspect ratio&lt;/h2&gt;
&lt;p&gt;The following &lt;code&gt;@supports&lt;/code&gt; at-rule will check if the browser supports the &lt;code&gt;sqrt()&lt;/code&gt; CSS function:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	To detect support for `sqrt()` we use `flex`,
	the shortest (afaict) CSS property that accepts 
	the `&amp;lt;number&gt;` value that `sqrt()` produces.
 */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@supports&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In browsers that don&#39;t support this function, we could enforce a fixed &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio&quot;&gt;&lt;code&gt;aspect-ratio&lt;/code&gt;&lt;/a&gt; and use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit&quot;&gt;&lt;code&gt;object-fit&lt;/code&gt;&lt;/a&gt; property to clip the image to fit, as &lt;a href=&quot;https://css-irl.info/aspect-ratio-is-great/&quot;&gt;described by Michelle Barker&lt;/a&gt;. To make the fallback comparable, we do have to precompute the square root of this fixed aspect ratio and factor it in — not ideal, but not exceedingly annoying. Here&#39;s the technique along with the fallback:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.ratio-gallery&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--basis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 300px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.ratio-gallery img&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--w&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--h&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--basis&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; / &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--h&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@supports&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token selector&quot;&gt;.ratio-gallery img&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--basis&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; * 1.224 &lt;span class=&quot;token comment&quot;&gt;/* = sqrt(3/2) */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;aspect-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3/2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;object-fit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cover&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a more developed demo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/image-sizes.html&quot;&gt;Demo: equally-sized images, with fallback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Turns out aspect-ratio-aware sizing is not too hard to pull off as long as we have control of image dimensions in the markup. The technique may be especially useful in content management systems where authors are able to upload arbitrary assets that need to look good next to each other, and where the author is not expected, or is unable, to fine-tune the sizes via custom CSS.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Addenda&lt;/h2&gt;
&lt;h3&gt;Defensive CSS&lt;/h3&gt;
&lt;p&gt;Šime helpfully pointed out that the basic technique does not handle extreme aspect ratios well. In fact, the &lt;em&gt;arbitrary&lt;/em&gt; in &lt;em&gt;arbitrary aspect ratio&lt;/em&gt; is the cue to put our &lt;a href=&quot;https://defensivecss.dev/&quot;&gt;defensive CSS&lt;/a&gt; hat on.&lt;/p&gt;
&lt;p&gt;We can limit the space the image takes in any one direction with &lt;code&gt;max-width&lt;/code&gt; and &lt;code&gt;max-height&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.ratio-gallery img&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--w&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--h&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--basis&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; / &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--h&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--basis&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; * 2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;max-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--basis&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; * 2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(You might also want to throw in an &lt;code&gt;object-fit: cover&lt;/code&gt; to handle very tall images that get squished when &lt;code&gt;max-height&lt;/code&gt; kicks in.)&lt;/p&gt;
&lt;p&gt;Here is the demo again, which includes the defensive CSS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/image-sizes.html&quot;&gt;Demo: equally-sized images, with fallback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Prior art&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Update Dec. 2022&lt;/strong&gt;: Piper Haywood had described back in 2020 &lt;a href=&quot;https://piperhaywood.com/images-consistent-surface-area/&quot;&gt;a technique for sizing images&lt;/a&gt; using the &lt;code&gt;sqrt()&lt;/code&gt; CSS function, at a time when the function was not yet supported in any browser. Nick Sherman produced a &lt;a href=&quot;https://nicksherman.com/size-by-area/&quot;&gt;nice demo&lt;/a&gt; for the technique, using an approximation of &lt;code&gt;sqrt()&lt;/code&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Button type matters, after all</title>
		<link href="https://danburzo.ro/button-type-button/" />
		<updated>2022-03-26T00:00:00Z</updated>
		<id>https://danburzo.ro/button-type-button/</id>
		<content type="html"
			>&lt;p&gt;The &lt;code&gt;&amp;lt;button type=&#39;button&#39;&amp;gt;&lt;/code&gt; construct is &lt;span class=&quot;sc&quot;&gt;HTML&lt;/span&gt;&#39;s &lt;em&gt;eat your veggies&lt;/em&gt;. It&#39;s good advice and you should always do it. Between you and me, we both know it&#39;s a bit silly: &lt;em&gt;of course a button is a button, I mean…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, &lt;em&gt;buddy&lt;/em&gt;, if you&#39;ve been omitting your &lt;code&gt;type&lt;/code&gt; attribute with impunity, I&#39;m here to tell you that one day, without warning, this habit is going to bite you in the ass. I know it because &lt;em&gt;*cue pensive piano music*&lt;/em&gt; it happened to me. One day I did a boo-boo and it only occurred to me a while later that the boo-boo was there.&lt;/p&gt;
&lt;p&gt;In all honesty, the actual event lacked the gravitas of my dramatization here, but I thought it was delightfully unintuitive form behavior so I posted about it on social media. Seeing other people go &lt;em&gt;huh&lt;/em&gt;, I thought I&#39;d unpack the idea a bit.&lt;/p&gt;
&lt;p&gt;Here is the markup of a typical JavaScript-enhanced log in form. Excuse the inline event listeners, which are used to make the code sample simpler:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onsubmit&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Username: &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Password: &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Log in&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;form-figure&quot;&gt;
&lt;form action=&quot;https://danburzo.ro/button-type-button/&quot; onsubmit=&quot;alert(&amp;quot;logging you in&amp;quot;); return false;&quot;&gt;
	&lt;p&gt;
		&lt;label&gt;Username: &lt;input type=&quot;text&quot; name=&quot;username&quot; /&gt;&lt;/label&gt;
	&lt;/p&gt;
	&lt;p&gt;
		&lt;label&gt;Password: &lt;input type=&quot;password&quot; name=&quot;password&quot; /&gt;&lt;/label&gt;
	&lt;/p&gt;
	&lt;button type=&quot;submit&quot;&gt;Log in&lt;/button&gt;
&lt;/form&gt;
&lt;/figure&gt;
&lt;p&gt;Now let&#39;s say you add to the form a button that reveals the password in plain text:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onsubmit&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Username: &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Password: &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onclick&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
			Show password
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Log in&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;form-figure&quot;&gt;
&lt;form action=&quot;https://danburzo.ro/button-type-button/&quot; onsubmit=&quot;alert(&amp;quot;logging you in&amp;quot;); return false;&quot;&gt;
	&lt;p&gt;
		&lt;label&gt;Username: &lt;input type=&quot;text&quot; name=&quot;username&quot; /&gt;&lt;/label&gt;
	&lt;/p&gt;
	&lt;p&gt;
		&lt;label&gt;Password: &lt;input type=&quot;password&quot; name=&quot;password&quot; /&gt;&lt;/label&gt;
		&lt;button onclick=&quot;this.form.password.type=&amp;quot;text&amp;quot;; return false;&quot;&gt;Show password&lt;/button&gt;
	&lt;/p&gt;
	&lt;button type=&quot;submit&quot;&gt;Log in&lt;/button&gt;
&lt;/form&gt;
&lt;/figure&gt;
&lt;p&gt;What unexpected behavior have you introduced to this form with the addition of that button?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; If the user presses the &lt;kbd&gt;Enter&lt;/kbd&gt; key after they&#39;ve typed in their password, instead of submitting the form, the user&#39;s password is revealed in plain text.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Show password&lt;/em&gt; button lacks an explicit &lt;code&gt;type=&#39;button&#39;&lt;/code&gt;and so it gets the default &lt;code&gt;type=&#39;submit&#39;&lt;/code&gt;. Outside of forms, this has no ill effect: without a form to submit, the button behaves like &lt;code&gt;type=&#39;button&#39;&lt;/code&gt; in that it does nothing. Put it into a form and it suddenly behaves &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type&quot;&gt;as an actual submit button&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When the user presses the &lt;kbd&gt;Enter&lt;/kbd&gt; key while focused on one of the form&#39;s inputs, the form gets submitted via the so-called &lt;em&gt;implicit submission&lt;/em&gt;. The browser doesn&#39;t simply submit the form, it &lt;strong&gt;actually clicks&lt;/strong&gt; the first submit button it finds in the form. From the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission&quot;&gt;HTML spec&lt;/a&gt; (emphasis mine):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the user agent supports letting the user submit a form implicitly (for example, on some platforms hitting the &amp;quot;enter&amp;quot; key while a text control is focused implicitly submits the form), then doing so for a form, whose default button &lt;strong&gt;[ie. the first submit button in tree order whose form owner is that form element]&lt;/strong&gt; has activation behavior and is not disabled, &lt;strong&gt;must cause the user agent to fire a &lt;code&gt;click&lt;/code&gt; event at that default button&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because the &lt;em&gt;Show password&lt;/em&gt; button is implicitly a submit button, and it happened to be added sooner in tree order than the legit submit button, the implicit submission reveals the user&#39;s password. Ta-daa!&lt;/p&gt;
&lt;p&gt;And &lt;em&gt;that&#39;s why&lt;/em&gt; you always write &lt;code&gt;&amp;lt;button type=&#39;button&#39;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Coda: actually marking up a login form&lt;/h2&gt;
&lt;p&gt;Besides the point I was trying to make about implicit form submission, there are more things that this toy markup misses, among which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete&quot;&gt;&lt;code&gt;autocomplete&lt;/code&gt; attribute&lt;/a&gt; can help password managers make better choices about storing and filling in information for the form.&lt;/li&gt;
&lt;li&gt;Users of assistive technology are not well served by the &lt;em&gt;Show password&lt;/em&gt; functionality. If you&#39;re serious about implementing this feature, Nicolas Steenhout touches on the topic in his &lt;a href=&quot;https://incl.ca/show-hide-password-accessibility-and-password-hints-tutorial/&quot;&gt;Show/Hide password accessibility and password hints tutorial&lt;/a&gt;, and the GDS document even more gotchas in &lt;a href=&quot;https://technology.blog.gov.uk/2021/04/19/simple-things-are-complicated-making-a-show-password-option/&quot;&gt;Simple things are complicated: making a show password option&lt;/a&gt;. But, more importantly, everyone &lt;a href=&quot;https://github.com/whatwg/html/issues/7293&quot;&gt;seems to agree&lt;/a&gt; that we would be better off if browsers themselves had the feature baked in.&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Observe an element&#39;s focus-within state</title>
		<link href="https://danburzo.ro/focus-within/" />
		<updated>2022-03-18T19:23:37Z</updated>
		<id>https://danburzo.ro/focus-within/</id>
		<content type="html"
			>&lt;p&gt;With this short tip, we&#39;ll devise a way to tell when a DOM element&#39;s &lt;em&gt;focus-within&lt;/em&gt; state changes, that is observing whenever the element, or any of its descendants, holds the focus.&lt;/p&gt;
&lt;p&gt;You may already be familiar with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within&quot;&gt;the CSS &lt;code&gt;:focus-within&lt;/code&gt; pseudo-class&lt;/a&gt; that provides such a hook:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Emphasize the container when it has focus within.
 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.container:focus-within&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px dotted currentColor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In JavaScript, you could &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/matches&quot;&gt;match that CSS pseudo-class&lt;/a&gt; or look at the containment of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement&quot;&gt;the active element&lt;/a&gt; to check if, at that particular point in time, the focus is within a DOM element:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Test if the container has focus within with either:

	* matching the :focus-within CSS pseudo-class
	* checking that the active element is contained within
 */&lt;/span&gt;
containerElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;:focus-within&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
containerElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those checks are great for one-off tests, but what if we want to observe &lt;em&gt;changes&lt;/em&gt; in the element&#39;s focus containment? While there are no specific DOM events for it, we&#39;ll build our own using the available focus events.&lt;/p&gt;
&lt;h2&gt;DOM focus events: a refresher&lt;/h2&gt;
&lt;p&gt;There are four events under the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent&quot;&gt;&lt;code&gt;FocusEvent&lt;/code&gt;&lt;/a&gt; umbrella that tell us when an object obtains or loses focus. Here they are, helpfully categorized like bottled water:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event&quot;&gt;&lt;code&gt;focusin&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event&quot;&gt;&lt;code&gt;focusout&lt;/code&gt;&lt;/a&gt; are the &lt;em&gt;bubbly events&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event&quot;&gt;&lt;code&gt;focus&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event&quot;&gt;&lt;code&gt;blur&lt;/code&gt;&lt;/a&gt; are the &lt;em&gt;still events&lt;/em&gt; (they don&#39;t, ahem, bubble).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&#39;t have the exact reasons for why the &lt;code&gt;focus&lt;/code&gt; and &lt;code&gt;blur&lt;/code&gt; events don&#39;t bubble up the DOM tree, but Peter-Paul Koch has &lt;a href=&quot;https://www.quirksmode.org/dom/events/blurfocus.html&quot;&gt;a plausible explanation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The reason focus and blur don’t bubble is that the events mean something quite different on the window and on any other focusable element. These two definitions should not be confused, and therefore the events cannot be allowed to bubble up to the document.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The great thing about all focus events, which makes many scenarios much simpler to manage, is that their &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/relatedTarget&quot;&gt;&lt;code&gt;relatedTarget&lt;/code&gt; property&lt;/a&gt; points to the &lt;em&gt;other relevant element&lt;/em&gt;. For events related to loss of focus (&lt;code&gt;blur&lt;/code&gt; and &lt;code&gt;focusout&lt;/code&gt;) the related target is the element that will receive the focus next, if any; for events signifying acquisition of focus (&lt;code&gt;focus&lt;/code&gt; and &lt;code&gt;focusin&lt;/code&gt;), the related target is the previously focused element, if any.&lt;/p&gt;
&lt;p&gt;Here&#39;s the sequence of events that gets triggered for when focus shifts from element &lt;span class=&quot;el-marker el-marker--a&quot;&gt;A&lt;/span&gt; to element &lt;span class=&quot;el-marker el-marker--b&quot;&gt;B&lt;/span&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Order&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;&lt;code&gt;.target&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;.relatedTarget&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blur&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--a&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--b&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;focusout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--a&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--b&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;focus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--b&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--a&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;focusin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--b&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class=&quot;el-marker el-marker--a&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All this being said, let&#39;s write some code.&lt;/p&gt;
&lt;h2&gt;Observing the focus-within state&lt;/h2&gt;
&lt;p&gt;Whenever the container, or one of its descendants, &lt;em&gt;receives&lt;/em&gt; focus, the &lt;code&gt;focusin&lt;/code&gt; event bubbles up the DOM tree to the container (and beyond). The event&#39;s &lt;code&gt;relatedTarget&lt;/code&gt; property points to the previously focused element, if any. To detect when the container &lt;em&gt;first&lt;/em&gt; receives focus from outside, we can check for containment:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;containerElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focusin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relatedTarget&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Focus was already in the container */&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Focus was received from outside the container */&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use the same check for containment to detect when focus &lt;em&gt;leaves&lt;/em&gt; the element, this time with the &lt;code&gt;focusout&lt;/code&gt; event. Since this event&#39;s &lt;code&gt;relatedTarget&lt;/code&gt; property now points to where the focus is heading next, the check works the other way around too:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;containerElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focusout&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relatedTarget&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Focus will still be within the container */&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Focus will leave the container */&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s it! Within a few short lines, we&#39;re already handling focus containment changes that cover a whole range of input methods (mouse, touch, keyboard).&lt;/p&gt;
&lt;h2&gt;Handling focus outside the page&lt;/h2&gt;
&lt;p&gt;When used as the trigger for hiding &lt;a href=&quot;https://adrianroselli.com/2021/07/stop-using-pop-up.html&quot;&gt;&lt;em&gt;pop-ups&lt;/em&gt;&lt;/a&gt; or &lt;a href=&quot;https://adrianroselli.com/2020/03/stop-using-drop-down.html&quot;&gt;&lt;em&gt;drop-downs&lt;/em&gt;&lt;/a&gt;, the basic &lt;code&gt;focusout&lt;/code&gt; implementation can occasionally be too eager: annoyingly, the element disappears before we can inspect it with the browser&#39;s developer tools.&lt;/p&gt;
&lt;p&gt;This is just one instance of the focus moving &lt;em&gt;outside&lt;/em&gt; the web page (without necessarily hiding the page). In these cases we might decide we want to keep the element visible.&lt;/p&gt;
&lt;p&gt;In the code sample below, we use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus&quot;&gt;the &lt;code&gt;Document.hasFocus()&lt;/code&gt; method&lt;/a&gt; to detect the lost focus and keep the element visible:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;containerElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focusout&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/*
		If the document has lost focus,
		skip the containment check 
		and keep the element visible.
	 */&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasFocus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relatedTarget&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;hideSelf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, once the element loses focus along with the document, and we just ignore the event and not hide the element, we run into the opposite problem: the element &lt;em&gt;just sits in our face&lt;/em&gt; when we get back and poke around the page. It doesn&#39;t know we&#39;re back.&lt;/p&gt;
&lt;p&gt;Before giving away the document&#39;s focus, we need to attach a listener for when, if ever, the focus returns. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event&quot;&gt;window &lt;code&gt;focus&lt;/code&gt; event&lt;/a&gt; — the non-bubbly kind — is perfect here, when paired with one of the two on-demand tests we mentioned in the introduction:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;containerElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focusout&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; self &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTarget&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/*
		If the document has lost focus,
		don&#39;t hide the container just yet,
		wait until the focus is returned.
	 */&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasFocus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focus&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;focusReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;/* 
				We want the listener to be triggered just once,
				so we have it remove itself from the `focus` event.
			*/&lt;/span&gt;
			window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focus&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; focusReturn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

			&lt;span class=&quot;token comment&quot;&gt;/*
				Test whether the container is still in the DOM 
				and whether the active element is contained within.
			 */&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isConnected &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;hideSelf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relatedTarget&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;hideSelf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A couple of notes on this listener:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because the &lt;code&gt;focus&lt;/code&gt; event that returns focus to the page can happen after a long time (or maybe never), it&#39;s useful to check that the element &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected&quot;&gt;is still in the DOM&lt;/a&gt; when it finally occurs.&lt;/li&gt;
&lt;li&gt;Since the event listener is set up to remove itself after the first invocation, we don&#39;t expect it to generate a memory leak. The &lt;code&gt;addEventListener()&lt;/code&gt; method does have the convenient &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters&quot;&gt;&lt;code&gt;{ once: true }&lt;/code&gt;&lt;/a&gt; option but the do-it-yourself way is more resilient. A browser that doesn&#39;t support the &lt;code&gt;once&lt;/code&gt; option would treat the function as a normal listener and keep it around &lt;em&gt;forever&lt;/em&gt; — leaks ahoy!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fixing some browser quirks&lt;/h2&gt;
&lt;h3&gt;Blurring in iOS Safari&lt;/h3&gt;
&lt;p&gt;In iOS Safari, tapping on non-focusable elements on the page won&#39;t blur the active element. It seems to need a new focus target to which to switch before it gives up its current focus. To make sure that the &lt;em&gt;tap outside to blur&lt;/em&gt; functionality works as expected, we can mark the body as focusable with &lt;code&gt;tabindex=&#39;-1&#39;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;tabindex&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;-1&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		…
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You can see the full code in action in the demo below, which compares the CSS &lt;code&gt;:focus-within&lt;/code&gt; pseudo-class with the JavaScript implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/focus-within.html&quot;&gt;Demo: Detect focus-within state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2021</title>
		<link href="https://danburzo.ro/favorite-records-2021/" />
		<updated>2022-01-02T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2021/</id>
		<content type="html"
			>&lt;img loading=&quot;lazy&quot; src=&quot;https://danburzo.ro/img/top-20-albums-2021.jpg&quot; alt=&quot;The covers of my top 20 albums of 2021 arranged in a grid&quot; width=&quot;1492&quot; height=&quot;1200&quot; /&gt;
&lt;p&gt;Here are my top 20 albums of 2021, in alphabetical order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://awvfts.bandcamp.com/album/invisible-cities&quot;&gt;&lt;strong&gt;A Winged Victory for the Sullen — Invisible Cities&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://akirarabelais.bandcamp.com/album/a-la-recherche-du-temps-perdu&quot;&gt;&lt;strong&gt;Akira Rabelais — À La Recherche Du Temps Perdu&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/the-wind-balmorhea-12219&quot;&gt;&lt;strong&gt;Balmorhea — The Wind&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://biosphere.bandcamp.com/album/angels-flight&quot;&gt;&lt;strong&gt;Biosphere — Angel&#39;s Flight&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dustinohalloran.bandcamp.com/album/silfur&quot;&gt;&lt;strong&gt;Dustin O&#39;Halloran — Silfur&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://elmichelsaffair.bandcamp.com/album/yeti-season&quot;&gt;&lt;strong&gt;El Michels Affair — Yeti Season&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL0r5wXgga2jLlt9y1OR66rhziHs-VjLYw&quot;&gt;&lt;strong&gt;Enfant Sauvage — Petrichor&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/1548025973&quot;&gt;&lt;strong&gt;Jay-Jay Johanson — Rorschach Test&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://josgonzlez.bandcamp.com/album/local-valley&quot;&gt;&lt;strong&gt;José González — Local Valley&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://indigoraw.bandcamp.com/album/microscope-of-heraclitus-reworked&quot;&gt;&lt;strong&gt;Kazuya Nagaya — Microscope of Heraclitus Reworked&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/fragments-ep/1575379050&quot;&gt;&lt;strong&gt;Keaton Henson — Fragments&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://meitei.bandcamp.com/album/kofu-ii-ii&quot;&gt;&lt;strong&gt;Meitei — Kofū II&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mogwai.bandcamp.com/album/as-the-love-continues&quot;&gt;&lt;strong&gt;Mogwai — As the Love Continues&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heavenlyrecordings.bandcamp.com/album/ive-been-trying-to-tell-you&quot;&gt;&lt;strong&gt;Saint Etienne — I&#39;ve Been Trying To Tell You&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/save-me-not/1554077598&quot;&gt;&lt;strong&gt;Sebastian Plano — Save Me Not&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://slowmeadow.bandcamp.com/album/upstream-dream&quot;&gt;&lt;strong&gt;Slow Meadow — Upstream Dream&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sofiakourtesis.bandcamp.com/album/fresia-magdalena&quot;&gt;&lt;strong&gt;Sofia Kourtesis — Fresia Magdalena&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://theweatherstation.bandcamp.com/album/ignorance&quot;&gt;&lt;strong&gt;The Weather Station — Ignorance&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://coldcut.bandcamp.com/album/0&quot;&gt;&lt;strong&gt;Various Artists — @0&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BDwAlto-NKU&quot;&gt;&lt;strong&gt;WhoMadeWho — Live at Abu Simbel, Egypt&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;More great albums released this year:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://alexsomers.bandcamp.com/album/siblings&quot;&gt;Alex Somers — Siblings&lt;/a&gt; and &lt;a href=&quot;https://alexsomers.bandcamp.com/album/siblings-2&quot;&gt;Siblings 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://altingun.bandcamp.com/album/yol&quot;&gt;Altın Gün — Yol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/ro/album/1588387718&quot;&gt;Alva Noto — HYbr:ID I&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soundcloud.com/tikitarec/sets/tikita012-1&quot;&gt;Amandra &amp;amp; Karim — Sqala&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/never-the-right-time/1557478238&quot;&gt;Andy Stott — Never the Right Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://angelolsen.bandcamp.com/album/aisles&quot;&gt;Angel Olsen — Aisles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arushijain.bandcamp.com/album/under-the-lilac-sky&quot;&gt;Arushi Jain — Under the Lilac Sky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bicep.bandcamp.com/album/isles&quot;&gt;Bicep — Isles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bigredmachine.bandcamp.com/album/how-long-do-you-think-its-gonna-last&quot;&gt;Big Red Machine — How Long Do You Think It&#39;s Gonna Last?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blanckmass.bandcamp.com/album/in-ferneaux&quot;&gt;Blanck Mass — In Ferneaux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blawan.bandcamp.com/album/woke-up-right-handed&quot;&gt;Blawan — Woke Up Right Handed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLsh4QmACcJ6dDPPizqq3Fp6L1WiU6Zrnv&quot;&gt;Bleachers — Take the Sadness Out of Saturday Night&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://burial.bandcamp.com/album/shock-power-of-love-ep&quot;&gt;Burial + Blackdown — Shock Power of Love&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://burial.bandcamp.com/album/chemz-dolphinz&quot;&gt;Burial — Chemz / Dolphinz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cassandrajenkins.bandcamp.com/album/an-overview-on-phenomenal-nature&quot;&gt;Cassandra Jenkins — An Overview on Phenomenal Nature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caterinabarbieri.bandcamp.com/track/knot-of-spirit&quot;&gt;Caterina Barbieri &amp;amp; Lyra Pramuk — Knot of Spirit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://colleencolleen.bandcamp.com/album/the-tunnel-and-the-clearing&quot;&gt;Colleen — The Tunnel and the Clearing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danielavery.bandcamp.com/album/together-in-static&quot;&gt;Daniel Avery — Together in Static&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://darkside.bandcamp.com/album/spiral&quot;&gt;&lt;span class=&quot;sc&quot;&gt;DARKSIDE&lt;/span&gt; — Spiral&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://davidwaltersplay.bandcamp.com/album/nocturne&quot;&gt;David Walters — Nocturne&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deafheavens.bandcamp.com/album/infinite-granite&quot;&gt;Deafheaven — Infinite Granite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devendrabanhart.bandcamp.com/album/refuge&quot;&gt;Devendra Banhart &amp;amp; Noah Georgeson — Refuge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://umorrex.bandcamp.com/album/spume-recollection&quot;&gt;Driftmachine — Spume &amp;amp; Recollection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://elmichelsaffair.bandcamp.com/album/the-abominable-ep&quot;&gt;El Michels Affair — The Abominable EP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thou.bandcamp.com/album/the-helm-of-sorrow&quot;&gt;Emma Ruth Rundle &amp;amp; Thou — The Helm of Sorrow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fabriziopaterlini.bandcamp.com/album/lifeblood&quot;&gt;Fabrizio Paterlini — LifeBlood&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flyinglotus.bandcamp.com/album/yasuke&quot;&gt;Flying Lotus — Yasuke&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kompakt-gas.bandcamp.com/album/der-lange-marsch&quot;&gt;&lt;span class=&quot;sc&quot;&gt;GAS&lt;/span&gt; — Der Lange Marsch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://godford.bandcamp.com/album/i-you-she&quot;&gt;Godford — &lt;span class=&quot;sc&quot;&gt;I YOU SHE&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://godspeedyoublackemperor.bandcamp.com/album/g-d-s-pee-at-state-s-end&quot;&gt;Godspeed You! Black Emperor — G_d&#39;s Pee &lt;span class=&quot;sc&quot;&gt;AT STATE&#39;S END!&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://grouper.bandcamp.com/album/shade/&quot;&gt;Grouper — Shade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://howdoesitfeeltobeloved.bandcamp.com/album/the-hill-the-light-the-ghost&quot;&gt;Haiku Salut — The Hill, The Light, The Ghost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shop.hammockmusic.com/album/elsewhere&quot;&gt;Hammock — Elsewhere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://haniarani.bandcamp.com/album/music-for-film-and-theatre&quot;&gt;Hania Rani — Music for Film and Theatre&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boomkat.com/products/dissent-1a961ac2-0108-4372-a21f-6c56affd7899&quot;&gt;Heinrich Köbberling, Laurel Halo, Moritz Von Oswald Trio — Dissent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heladonegro.bandcamp.com/album/far-in&quot;&gt;Helado Negro — Far In&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/1557713010&quot;&gt;Jean-Michel Jarre — Amazônia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jonhopkins.bandcamp.com/album/music-for-psychedelic-therapy&quot;&gt;Jon Hopkins — Music for Psychedelic Therapy&lt;/a&gt; and &lt;a href=&quot;https://music.apple.com/ro/album/piano-versions-ep/1557647242&quot;&gt;Piano Versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://johannjohannsson.bandcamp.com/album/gold-dust&quot;&gt;Jóhann Jóhannsson — Gold Dust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jonsi.bandcamp.com/album/obsidian&quot;&gt;Jónsi — Obsidian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kangdingray.bandcamp.com/album/branches&quot;&gt;kangding ray — Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/chemtrails-over-the-country-club/1545567745&quot;&gt;Lana Del Rey — Chemtrails Over the Country Club&lt;/a&gt; and &lt;a href=&quot;https://music.apple.com/ro/album/blue-banisters/1584675130&quot;&gt;Blue Banisters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kmru.bandcamp.com/album/logue-2&quot;&gt;&lt;span class=&quot;sc&quot;&gt;KMRU&lt;/span&gt; — Logue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://theliminanas.bandcamp.com/album/de-pel-cula&quot;&gt;Laurent Garnier &amp;amp; The Limiñanas — De Película&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://librarytapes.bandcamp.com/album/dusk&quot;&gt;Library Tapes — Dusk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/burn/1549003458&quot;&gt;Lisa Gerrard &amp;amp; Jules Maxwell — Burn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://falseidols.bandcamp.com/album/lonely-guest&quot;&gt;Lonely Guest — Lonely Guest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/ro/album/1548726031&quot;&gt;Marianne Faithfull — She Walks in Beauty (with Warren Ellis)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://store.deutschegrammophon.com/p51-i0028948604463/max-richter/exiles-2lp-/index.html&quot;&gt;Max Richter, Kristjan Järvi, Baltic Sea Philharmonic — Exiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://murcofmusic.bandcamp.com/album/the-alias-sessions&quot;&gt;Murcof — The Alias Sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/carnage/1552929712&quot;&gt;Nick Cave &amp;amp; Warren Ellis — &lt;span class=&quot;sc&quot;&gt;CARNAGE&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nightmaresonwax.bandcamp.com/album/shout-out-to-freedom&quot;&gt;Nightmares On Wax — Shout Out! To Freedom...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/old-friends-new-friends&quot;&gt;Nils Frahm — Old Friends New Friends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=OLAK5uy_kuVSVULMo3GB_kgsN7r96_AsUcYktsQ4Y&quot;&gt;Ólafur Arnalds — When We Are Born&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://perfumegenius.bandcamp.com/album/immediately-remixes&quot;&gt;Perfume Genius — &lt;span class=&quot;sc&quot;&gt;IMMEDIATELY&lt;/span&gt; Remixes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://perilazone.bandcamp.com/album/how-much-time-it-is-between-you-and-me&quot;&gt;Perila — How Much Time it is Between You and Me?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kida-mnesia.com/&quot;&gt;Radiohead — &lt;span class=&quot;sc&quot;&gt;KID A MNESIA&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ryuichisakamotoanddavidtoop.bandcamp.com/album/garden-of-shadows-and-light&quot;&gt;Ryuichi Sakamoto &amp;amp; David Toop — Garden of Shadows and Light&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sarahdavachi.bandcamp.com/album/antiphonals&quot;&gt;Sarah Davachi — Antiphonals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://slowmeadow.bandcamp.com/album/happy-occident-reworks&quot;&gt;Slow Meadow — Happy Occident Reworks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.sufjan.com/album/a-beginners-mind&quot;&gt;Sufjan Stevens &amp;amp; Angelo De Augustine — A Beginner&#39;s Mind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.sufjan.com/album/convocations&quot;&gt;Sufjan Stevens — Convocations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sunn.bandcamp.com/album/metta-benevolence-bbc-6music-live-on-the-invitation-of-mary-anne-hobbs&quot;&gt;Sunn O))) — Metta, Benevolence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://duststoredigital.com/album/music-for-photographers&quot;&gt;The Black Dog — Music For Photographers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/us/album/love-signs/1568231755&quot;&gt;The Jungle Giants — Love Signs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/1576851901&quot;&gt;The War on Drugs — I Don’t Live Here Anymore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tindersticks.bandcamp.com/album/distractions&quot;&gt;Tindersticks — Distractions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v-twin.bandcamp.com/album/ookii-gekkou&quot;&gt;Vanishing Twin — Ookii Gekkou&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/welfare-jazz/1533668629&quot;&gt;Viagra Boys — Welfare Jazz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vladislavdelay.bandcamp.com/album/rakka-ii&quot;&gt;Vladislav Delay — Rakka II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://xiuxiu.bandcamp.com/album/oh-no&quot;&gt;Xiu Xiu — &lt;span class=&quot;sc&quot;&gt;OH NO&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Found after the year was over:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://enzofavata.bandcamp.com/album/enzo-favata-the-crossing-2&quot;&gt;Enzo Favata The Crossing&lt;/a&gt; (found 2023)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other 2021 lists: &lt;a href=&quot;https://www.roughtrade.com/gb/albums-of-the-year-2021&quot;&gt;Rough Trade UK&lt;/a&gt;, &lt;a href=&quot;https://www.roughtrade.com/us/albums-of-the-year-2021-e9439d65-16e2-410d-9176-f4fa18db01d7&quot;&gt;Rough Trade US&lt;/a&gt;, &lt;a href=&quot;https://bleep.com/top-10-albums-of-the-year-2021&quot;&gt;Bleep&lt;/a&gt;, &lt;a href=&quot;https://thequietus.com/articles/30903-the-quietus-top-100-albums-of-2021-norman-records&quot;&gt;The Quietus&lt;/a&gt;, &lt;a href=&quot;https://boomkat.com/charts/boomkat-end-of-year-charts-2021&quot;&gt;Boomkat&lt;/a&gt;, &lt;a href=&quot;https://www.thewire.co.uk/audio/tracks/the-wire-s-releases-of-the-year-2021&quot;&gt;The Wire&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Pinch me, I&#39;m zooming: gestures in the DOM</title>
		<link href="https://danburzo.ro/dom-gestures/" />
		<updated>2021-11-07T00:00:00Z</updated>
		<id>https://danburzo.ro/dom-gestures/</id>
		<content type="html"
			>&lt;p&gt;Interpreting multi-touch user gestures on the web is not as straightforward as you&#39;d imagine.&lt;/p&gt;
&lt;p&gt;In this article we look at how current major browsers behave (and misbehave) in regards to gestures, and piece together a workable solution that uses &lt;code&gt;wheel&lt;/code&gt;, &lt;code&gt;touch&lt;/code&gt;, and &lt;code&gt;gesture&lt;/code&gt; DOM events.&lt;/p&gt;
&lt;details&gt;
	&lt;summary&gt;Table of contents&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#gesture-anatomy&quot;&gt;Anatomy of a gesture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#dom-events&quot;&gt;The relevant DOM events: wheel, touch, and gesture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#browser-tests&quot;&gt;Putting browsers to the test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#unify-wheel-touch-gesture&quot;&gt;Unifying wheel, touch, and gesture events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#apply-transform&quot;&gt;Applying the transformation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#further-refinements&quot;&gt;Refining the implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#questions-answers&quot;&gt;Questions &amp;amp; Answers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/dom-gestures/#revisions&quot;&gt;Revisions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2 id=&quot;gesture-anatomy&quot;&gt;Anatomy of a gesture&lt;/h2&gt;
&lt;p&gt;Two-finger gestures on touchscreens and modern trackpads allow users to manipulate on-screen elements as if they were physical objects: to move them and spin them around, to bring them closer or push them further away. Such a gesture encodes a &lt;a href=&quot;https://math.stackexchange.com/questions/716147/find-2d-affine-transform-matrix-given-a-pair-of-points&quot;&gt;combination of translation, uniform scaling, and rotation&lt;/a&gt; to apply to the target element. This is also known as an (affine) linear transformation.&lt;/p&gt;
&lt;p&gt;The impression of &lt;a href=&quot;https://en.wikipedia.org/wiki/Direct_manipulation_interface&quot;&gt;direct manipulation&lt;/a&gt; is created when the transformation &lt;a href=&quot;https://material.io/design/interaction/gestures.html&quot;&gt;maps naturally&lt;/a&gt; to the movement of the touchpoints. There are ideas for &lt;a href=&quot;http://dx.doi.org/10.1145/2642918.2647352&quot;&gt;novel ways to interpret a gesture&lt;/a&gt;, but operating systems have settled on a mapping which keeps the parts you touch underneath your fingertips throughout the gesture. This is the approach we&#39;re most familiar with, and the one we will explore in this article.&lt;/p&gt;
&lt;p&gt;Let&#39;s see how a two-finger gesture maps to the basic components of a linear transformation.&lt;/p&gt;
&lt;img src=&quot;https://danburzo.ro/img/pinch-zoom/scale.png&quot; alt=&quot;Two touchpoints brought together, a solid line marking the distance between them.&quot; width=&quot;384&quot; height=&quot;384&quot; /&gt;
&lt;p&gt;&lt;strong&gt;Scale.&lt;/strong&gt; The change in distance between the two touchpoints throughout the gesture dictates the scale: if the fingers are brought together to half the initial distance, the object should be made half its original size.&lt;/p&gt;
&lt;img src=&quot;https://danburzo.ro/img/pinch-zoom/rotation.png&quot; alt=&quot;Two touchpoints being rotated, a solid line marking the distance between them. A dotted line shows the previous distance between the touchpoints. The two lines create an angle, corresponding to the rotation.&quot; width=&quot;384&quot; height=&quot;384&quot; /&gt;
&lt;p&gt;&lt;strong&gt;Rotation.&lt;/strong&gt; The slope defined by the two touchpoints dictates the rotation to be applied to the object.&lt;/p&gt;
&lt;img src=&quot;https://danburzo.ro/img/pinch-zoom/translation.png&quot; alt=&quot;Two touchpoints being moved in parallel, a solid line marking the distance between them. A dotted line marks the previous distance between the touchpoints. An arrow pointing from the midpoint of the dotted line to the midpoint of the solid line illustrates the origin and translation of the transformation.&quot; width=&quot;384&quot; height=&quot;384&quot; /&gt;
&lt;p&gt;&lt;strong&gt;Origin and translation.&lt;/strong&gt; The &lt;em&gt;midpoint&lt;/em&gt;, located halfway between the two touchpoints, has a double role: its initial coordinates establish the transformation origin, and its movement throughout the gesture translates the object accordingly.&lt;/p&gt;
&lt;p&gt;Native applications on touch-enabled devices have to access to &lt;a href=&quot;https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures&quot;&gt;high-level APIs&lt;/a&gt; that provide the translation, scale, rotation, and origin of a user gesture directly. On the web, we have to glue together several types of events to get a similar results across a variety of platforms.&lt;/p&gt;
&lt;h2 id=&quot;dom-events&quot;&gt;The relevant DOM events: wheel, touch, and gesture&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent&quot;&gt;&lt;code&gt;WheelEvent&lt;/code&gt;&lt;/a&gt; is triggered when the user intends to scroll an element with the mousewheel (from which the interface takes its name), a separate &amp;quot;scroll area&amp;quot; on older trackpads, or the entire surface area of newer trackpads with the two-finger vertical movement.&lt;/p&gt;
&lt;p&gt;Wheel events have &lt;code&gt;deltaX&lt;/code&gt;, &lt;code&gt;deltaY&lt;/code&gt;, and &lt;code&gt;deltaZ&lt;/code&gt; properties to encode the displacement dictated by the input device, and a &lt;code&gt;deltaMode&lt;/code&gt; to establish the unit of measurement:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;deltaMode&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DOM_DELTA_PIXEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scroll a precise amount of pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DOM_DELTA_LINE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scroll by lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DOM_DELTA_PAGE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scroll by entire pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As pinch gestures on trackpads became more commonplace, browser implementers needed a way support them in desktop browsers. Kenneth Auchenberg, in his &lt;a href=&quot;https://medium.com/@auchenberg/detecting-multi-touch-trackpad-gestures-in-javascript-a2505babb10e&quot;&gt;article on detecting multi-touch trackpad gestures&lt;/a&gt;, brings together key pieces of the story.&lt;/p&gt;
&lt;p&gt;In short, Chrome settled on &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=289887&quot;&gt;an approach&lt;/a&gt; inspired by Internet Explorer: encode pinch gestures as &lt;code&gt;wheel&lt;/code&gt; events with &lt;code&gt;ctrlKey: true&lt;/code&gt;, and the &lt;code&gt;deltaY&lt;/code&gt; property holding the proposed scale increment. Firefox eventually &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1052253&quot;&gt;did the same&lt;/a&gt;, and with Microsoft Edge recently having switched to Chromium as its underlying engine, we have a &amp;quot;standard&amp;quot; of sorts. I use scare-quotes because, as we&#39;ll see in the next section, some aspects don&#39;t quite match across browsers.&lt;/p&gt;
&lt;p&gt;Sometime between Chrome and Firefox adding support for pinch-zoom, &lt;a href=&quot;https://webkit.org/blog/6008/new-web-features-in-safari/&quot;&gt;Safari 9.1&lt;/a&gt; brought &lt;a href=&quot;https://developer.apple.com/documentation/webkitjs/gestureevent&quot;&gt;its very own &lt;code&gt;GestureEvent&lt;/code&gt;&lt;/a&gt;, which exposes precomputed &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;rotation&lt;/code&gt; properties, to the desktop.&lt;/p&gt;
&lt;p&gt;To this day, Safari remains the only browser that implements &lt;code&gt;GestureEvent&lt;/code&gt;, even among browsers on touch-enabled platforms. Instead, mobile browsers produce the lower-level &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Touch_events&quot;&gt;&lt;code&gt;TouchEvent&lt;/code&gt;s&lt;/a&gt;, which encode the positions of individual touchpoints in a gesture. They allow us, with a bit more effort than is required with higher-level events, to compute all the components of the linear transformation ourselves.&lt;/p&gt;
&lt;p&gt;Whereas &lt;code&gt;WheelEvent&lt;/code&gt; only encodes scale, and &lt;code&gt;GestureEvent&lt;/code&gt; includes scale and rotation, &lt;code&gt;TouchEvent&lt;/code&gt; lets us capture the translation as well, giving us finer-grained control over how we interpret the gesture.&lt;/p&gt;
&lt;p&gt;Combined &lt;code&gt;wheel&lt;/code&gt;, &lt;code&gt;gesture&lt;/code&gt; and &lt;code&gt;touch&lt;/code&gt; events seem sufficient to handle two-finger gestures across a variety of platforms. Let&#39;s see how this intuition, *ahem*, pans out.&lt;/p&gt;
&lt;h2 id=&quot;browser-tests&quot;&gt;Putting browsers to the test&lt;/h2&gt;
&lt;p&gt;I&#39;ve put together a test page that logs relevant properties of all the wheel, gesture, and touch events it captures:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/logger.html&quot;&gt;&lt;strong&gt;DOM Gesture Logger&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve tested a series of scrolls and pinches in recent versions of Firefox, Chrome, Safari, and Edge (Chromium-based), on a variety of devices I managed to procure for this purpose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a MacBook Pro (macOS Big Sur);&lt;/li&gt;
&lt;li&gt;a Surface Laptop with a touchscreen and built-in &lt;a href=&quot;https://www.howtogeek.com/286905/what-is-a-precision-touchpad-on-windows-pcs/&quot;&gt;precision touchpad&lt;/a&gt; (Windows 10);&lt;/li&gt;
&lt;li&gt;an ASUS notebook with a non-precision touchpad (Windows 10);&lt;/li&gt;
&lt;li&gt;an iPhone (iOS 14);&lt;/li&gt;
&lt;li&gt;an iPad with a keyboard (iPadOS 14); and&lt;/li&gt;
&lt;li&gt;an external mouse to connect to all laptops.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s dig into a few of the results, and how they inform our solution.&lt;/p&gt;
&lt;h3&gt;Results on macOS&lt;/h3&gt;
&lt;p&gt;When you perfrom a pinch-zoom gesture, Firefox and Chrome produce a &lt;code&gt;wheel&lt;/code&gt; event with a &lt;code&gt;deltaY: ±scale, ctrlKey: true&lt;/code&gt;. They produce an identical result when you scroll normally with two fingers while physically pressing down the &lt;kbd&gt;Ctrl&lt;/kbd&gt; key, with the difference that the latter is subject to inertial scrolling.&lt;/p&gt;
&lt;p&gt;Safari reacts to the proprietary &lt;code&gt;gesturestart&lt;/code&gt;, &lt;code&gt;gesturechange&lt;/code&gt;, and &lt;code&gt;gestureend&lt;/code&gt; events and produces a precomputed &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;rotation&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In all browsers, &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt;, as well as the position of the on-screen cursor, remain constant throughout two-finger gestures. This pair of coordinates establishes the gesture origin.&lt;/p&gt;
&lt;p id=&quot;wheel-behavior&quot;&gt;
	In the process of testing various modifier keys, I noted some default browser behaviors that you&#39;ll likely need to deflect with &lt;code&gt;event.preventDefault()&lt;/code&gt;:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;Option + wheel&lt;/kbd&gt; in Firefox navigates (or rather &lt;em&gt;flies&lt;/em&gt;) through the browser history; this would make sense for discrete steps on a mousewheel, but feels too weird to be useful on an inertial trackpad.&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Command + wheel&lt;/kbd&gt; in Firefox zooms in and out of the page, similarly to the &lt;kbd&gt;Command +&lt;/kbd&gt; and &lt;kbd&gt;Command -&lt;/kbd&gt; keyboard shortcuts;&lt;/li&gt;
&lt;li&gt;Pinching inwards in Safari minimizes the tab into a tab overview screen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;External, third-party mice are a different matter. Instead of the smooth pixel increments on the trackpad, the mouse&#39;s wheel jumps entire &lt;em&gt;lines&lt;/em&gt; at a time. (The &lt;samp&gt;Scrolling speed&lt;/samp&gt; setting in &lt;samp&gt;System Preferences &amp;gt; Mouse&lt;/samp&gt; controls how many.)&lt;/p&gt;
&lt;p&gt;Accordingly, Firefox shows &lt;code&gt;deltaY: ±1, deltaMode: DOM_DELTA_LINE&lt;/code&gt; for a tick of the wheel. This is the first, and at least on macOS the only, encounter with &lt;code&gt;DOM_DELTA_LINE&lt;/code&gt;. Chrome and Safari stick with &lt;code&gt;deltaMode: DOM_DELTA_PIXEL&lt;/code&gt; and a much larger &lt;code&gt;deltaY&lt;/code&gt;, sometimes hundreds of pixels at a time. This is an instance of the &lt;em&gt;many more pixels than expected&lt;/em&gt; deviation of which we&#39;ll see more throughout the test session. A basic pinch-zoom implementation that doesn&#39;t account for this quirk will zoom in and out in large, hard-to-control strides when you use the mousewheel.&lt;/p&gt;
&lt;p&gt;In all three browsers, &lt;code&gt;deltaX&lt;/code&gt; is normally zero. Holding down the &lt;kbd&gt;Shift&lt;/kbd&gt; key, a common way for users of an external mouse to scroll horizontally, swaps deltas and &lt;code&gt;deltaY&lt;/code&gt; becomes zero instead.&lt;/p&gt;
&lt;h3&gt;Results on Windows&lt;/h3&gt;
&lt;p&gt;A precision touchpad works on Windows similarly to the Magic Trackpad on macOS: Firefox, Chrome, and Edge produce results comparable to what we&#39;ve seen on macOS. The quirks emerge with non-precision touchpads and external mice, however.&lt;/p&gt;
&lt;p&gt;On Windows, the wheel of an external mouse has two scroll modes: either &lt;code&gt;L&lt;/code&gt; lines at a time (with a configurable &lt;code&gt;L&lt;/code&gt;), or a whole &lt;em&gt;page&lt;/em&gt; at a time.&lt;/p&gt;
&lt;p&gt;When you use the external mouse with line-scrolling, Firefox produces the expected &lt;code&gt;deltaY: ±L, deltaMode: DOM_DELTA_LINE&lt;/code&gt;. Chrome generates &lt;code&gt;deltaY: ±L * N, deltaMode: DOM_DELTA_PIXEL&lt;/code&gt;, where &lt;code&gt;N&lt;/code&gt; is a multiplier dictated by the browser, and which varies by machine: I&#39;ve seen &lt;code&gt;33px&lt;/code&gt; on the ASUS laptop and &lt;code&gt;50px&lt;/code&gt; on the Surface. (There&#39;s probably an inner logic to what&#39;s going on, but it doesn&#39;t warrant further investigation at this point.) Edge produces &lt;code&gt;deltaY: ±100, deltaMode: DOM_DELTA_PIXEL&lt;/code&gt;, so &lt;code&gt;100px&lt;/code&gt; regardless on the number of lines &lt;code&gt;L&lt;/code&gt; that the mouse is configured to scroll. With page-scrolling, browsers uniformly report &lt;code&gt;deltaY: ±1, deltaMode: DOM_DELTA_PAGE&lt;/code&gt;. None of the three browsers support holding down the &lt;kbd&gt;Shift&lt;/kbd&gt; to reverse the scroll axis of the mousewheel.&lt;/p&gt;
&lt;p&gt;On non-precision touchpads, the effect of scrolling on the primary (vertical) axis will mostly be equivalent to that of a mousewheel. The behavior of the secondary (horizontal) axis will not necessarily match it. At least on the machines on which I performed the tests, mouse settings also apply to the touchpad, even when there was no external mouse attached.&lt;/p&gt;
&lt;p&gt;In Firefox, in line-scrolling mode, scrolls on both axes produce &lt;code&gt;deltaMode: DOM_DELTA_LINE&lt;/code&gt; with &lt;code&gt;deltaX&lt;/code&gt; and &lt;code&gt;deltaY&lt;/code&gt;, respectively, containing a fraction of a line; a pinch gesture produces a constant &lt;code&gt;deltaY: ±L, deltaMode: DOM_DELTA_LINE, ctrlKey: true&lt;/code&gt;. In page-scrolling mode, scrolls on the primary axis produce &lt;code&gt;deltaMode: DOM_DELTA_PAGE&lt;/code&gt;, while on the secondary axis it remains in &lt;code&gt;deltaMode: DOM_DELTA_LINE&lt;/code&gt;; the pinch gesture produces &lt;code&gt;deltaY: ±1, deltaMode: DOM_DELTA_PAGE, ctrlKey: true&lt;/code&gt;. In Chrome, a surprising result is that when you scroll on the secondary axis you get &lt;code&gt;deltaX: 0, deltaY: N * ±L, shiftKey: true&lt;/code&gt;. Otherwise, the effects seen with a non-precision touchpad on Windows are of the &lt;em&gt;unexpected &lt;code&gt;deltaMode&lt;/code&gt;&lt;/em&gt; or &lt;em&gt;unexpected &lt;code&gt;deltaY&lt;/code&gt; value&lt;/em&gt; varieties.&lt;/p&gt;
&lt;h2 id=&quot;unify-wheel-touch-gesture&quot;&gt;Unifying wheel, touch, and gesture events&lt;/h2&gt;
&lt;p&gt;The API that unifies wheel, touch, and gesture events should look a lot like Safari&#39;s &lt;code&gt;GestureEvent&lt;/code&gt;. We will model it around three callbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;startGesture(gesture)&lt;/code&gt;, invoked at the beginning of the transformation;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;doGesture(gesture)&lt;/code&gt;, invoked continously throughout the transformation;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endGesture(gesture)&lt;/code&gt;, invoked at the end of the transformation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;gesture&lt;/code&gt; object should describe the transformation completely:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; … &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; … &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; …
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s see how each type of event maps to this setup.&lt;/p&gt;
&lt;h3 id=&quot;handle-gesture-events&quot;&gt;Processing Safari&#39;s gesture events&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;GestureEvent&lt;/code&gt;s are the most straightforward to map, so we&#39;ll start with those.&lt;/p&gt;
&lt;p&gt;We pick &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;rotation&lt;/code&gt; properties directly from the event object, and fill in the &lt;code&gt;origin&lt;/code&gt; based on the mouse coordinates. Since these types of events don&#39;t include a translation, we assume there to be &lt;em&gt;no translation&lt;/em&gt;, that is &lt;code&gt;translation: { x: 0, y: 0 }&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Handle Safari GestureEvents */&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gesturestart&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rotation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gesturechange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rotation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gestureend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rotation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An occasional misfire where the &lt;code&gt;gestureend&lt;/code&gt; event is emitted twice at the end of a gesture (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=233137&quot;&gt;WebKit#233137&lt;/a&gt;) is worked around with the &lt;code&gt;gesture&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Safari also has a built-in &lt;em&gt;pinch to show all tabs&lt;/em&gt; gesture which normally gets canceled when we call &lt;code&gt;preventDefault()&lt;/code&gt; on the gesture events. However, when the user combines a scale and rotation gesture, the browser behavior will be triggered nonetheless (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=233141&quot;&gt;WebKit#233141&lt;/a&gt;). I don&#39;t have a workaround yet, but would love to know if there is one.&lt;/p&gt;
&lt;details&gt;
	&lt;summary&gt;Sidenote: Passive event listeners&lt;/summary&gt;
&lt;p&gt;It used to be the case that we could &lt;code&gt;preventDefault()&lt;/code&gt; most events from any listener. This forces the browser to always be ready to have its default behavior overriden, which in some situations (scrolling, touch events) &lt;a href=&quot;https://dom.spec.whatwg.org/#observing-event-listeners&quot;&gt;leads to less than ideal performance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To mitigate this, the &lt;code&gt;passive&lt;/code&gt; flag was introduced as an option for &lt;code&gt;addEventListener()&lt;/code&gt;. To be able to prevent the default browser behavior, you&#39;d need to register an &lt;em&gt;active&lt;/em&gt; event listener by sending in the &lt;code&gt;{ passive: false }&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;For backwards compatibility, events are active by default. But, in some browsers for some performance-critical contexts, &lt;a href=&quot;https://www.chromestatus.com/feature/6662647093133312&quot;&gt;they default to being passive&lt;/a&gt;. To make sure our event listeners work as expected (now, and going forwaRD), throughout the code samples we use &lt;code&gt;{ passive: false }&lt;/code&gt; explicitly whenever we want to call &lt;code&gt;preventDefault()&lt;/code&gt;, first checking that the event is &lt;code&gt;cancelable&lt;/code&gt;.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Starting with Safari 15, macOS Safari also emits &lt;code&gt;wheel&lt;/code&gt; events for the pinch-zoom gesture (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=225788&quot;&gt;WebKit#225788&lt;/a&gt;). If you&#39;re only interested in scale transformations, in the future you&#39;ll be able listen to &lt;code&gt;wheel&lt;/code&gt; events, as shown in the next section, instead of relying on the non-standard &lt;code&gt;GestureEvent&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Converting wheel events to gestures&lt;/h3&gt;
&lt;p&gt;For wheel events, we have a few things to figure out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How to normalize the various ways browsers emit &lt;code&gt;wheel&lt;/code&gt; events into an uniform delta value.&lt;/li&gt;
&lt;li&gt;How to map the occurrence of &lt;code&gt;wheel&lt;/code&gt; events to &lt;code&gt;startGesture&lt;/code&gt;, &lt;code&gt;doGesture&lt;/code&gt;, and &lt;code&gt;endGesture&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;How to compute the &lt;code&gt;scale&lt;/code&gt; value from the delta.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s explore each sub-problem one by one.&lt;/p&gt;
&lt;h4&gt;1. Normalizing &lt;code&gt;wheel&lt;/code&gt; events&lt;/h4&gt;
&lt;p&gt;Our goal here is to implement a &lt;code&gt;normalizeWheelEvent&lt;/code&gt; function as described below:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Normalizes WheelEvent `e`,
	returning an array of deltas `[dx, dy]`.
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;normalizeWheelEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deltaX&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deltaY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// TODO: normalize dx, dy&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is where we can put our experimental browser data to good use. Let&#39;s recap some findings relevant to normalizing &lt;code&gt;wheel&lt;/code&gt; events.&lt;/p&gt;
&lt;p&gt;The browser may emit &lt;code&gt;deltaX: 0, deltaY: N, shiftKey: true&lt;/code&gt; when scrolling horizontally. We want to interpret this as &lt;code&gt;deltaX: N, deltaY: 0&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// swap `dx` and `dy`:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dx &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shiftKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dx&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, the browser may emit values in a &lt;code&gt;deltaMode&lt;/code&gt; other than pixels; for each, we need a multiplier:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deltaMode &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; WheelEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DOM_DELTA_LINE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	dx &lt;span class=&quot;token operator&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	dy &lt;span class=&quot;token operator&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deltaMode &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; WheelEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DOM_DELTA_PAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	dx &lt;span class=&quot;token operator&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	dy &lt;span class=&quot;token operator&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The choice of multipliers ultimately depends on the application. We might take inspiration &lt;a href=&quot;https://stackoverflow.com/questions/20110224/what-is-the-height-of-a-line-in-a-wheel-event-deltamode-dom-delta-line&quot;&gt;from browsers themselves&lt;/a&gt; or other tools the user may be familiar with; a document viewer may respect the mouse configuration to scroll one page at a time; map-pinching, on the other hand, may benefit from smaller increments.&lt;/p&gt;
&lt;p&gt;Finally, a browser may not emit &lt;code&gt;DOM_DELTA_LINE&lt;/code&gt; or &lt;code&gt;DOM_DELTA_PAGE&lt;/code&gt; where the input device settings dictate them, and instead offer a premultiplied value in &lt;code&gt;DOM_DELTA_PIXEL&lt;/code&gt;s. This value is often very large, &lt;code&gt;100px&lt;/code&gt; or more at a time.&lt;/p&gt;
&lt;p&gt;Why would browsers do that? With a whole lot of code out there that fails to look at the &lt;code&gt;deltaMode&lt;/code&gt;, minuscule &lt;code&gt;DOM_DELTA_LINE&lt;/code&gt; / &lt;code&gt;DOM_DELTA_PAGE&lt;/code&gt; increments that get incorrectly interpreted as pixels would make for lackluster scrolls. Browsers can be excused for trying to give a helping hand, but premultiplied pixel values — often computed in a way that only works if you think of &lt;code&gt;wheel&lt;/code&gt; events as signifying scroll intents — makes them harder to use for other purposes.&lt;/p&gt;
&lt;p&gt;Thankfully, even in the absence of a more sophisticated approach, simply setting the upper limit of &lt;code&gt;deltaY&lt;/code&gt; to something reasonable (e.g. &lt;code&gt;24px&lt;/code&gt;), just to pull the breaks on wild zooms, can go a long way towards improving the experience:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Note: we&#39;re using `Math.sign()` and `Math.min()` 
	to impose a maximum on the *absolute* value 
	of a possibly-negative number.
 */&lt;/span&gt;
dy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These few adjustments should cover a vast array of variations across browsers and devices. Yay compromise!&lt;/p&gt;
&lt;h4&gt;2. Generating gesture events from &lt;code&gt;wheel&lt;/code&gt; events&lt;/h4&gt;
&lt;p&gt;With normalization out of the way, the next obstacle is that &lt;code&gt;wheel&lt;/code&gt; events are individual occurrences, for which we must devise a &amp;quot;start&amp;quot; and &amp;quot;end&amp;quot; if we want to call &lt;code&gt;startGesture&lt;/code&gt; and &lt;code&gt;endGesture&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first &lt;code&gt;wheel&lt;/code&gt; event marks the beginning of a gesture. But what about the end? In line with keeping things simple, we consider a gesture done once a number of milliseconds pass since the last &lt;code&gt;wheel&lt;/code&gt; event. An outline for batching wheel events into gestures is listed below:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;wheel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/*
		If we don&#39;t have a `gesture`, it means
		we&#39;re just starting a batch of `wheel` events.
	 */&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; … &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/*
		Perform the current wheel gesture
	 */&lt;/span&gt;
	gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; … &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/*
		When the time runs out, call `endGesture`.
	 */&lt;/span&gt;
	timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// timeout in milliseconds&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual &lt;code&gt;gesture&lt;/code&gt; arguments to &lt;code&gt;startGesture&lt;/code&gt;, &lt;code&gt;doGesture&lt;/code&gt;, and &lt;code&gt;endGesture&lt;/code&gt; functions are explored in the next section.&lt;/p&gt;
&lt;h4&gt;3. Converting the wheel delta to a &lt;code&gt;scale&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;In Safari, a &lt;code&gt;gesturechange&lt;/code&gt; event&#39;s &lt;code&gt;scale&lt;/code&gt; property holds the &lt;em&gt;accumulated&lt;/em&gt; scale to apply to the object at each moment of the gesture:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;final_scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; initial_scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In fact, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures/handling_pinch_gestures&quot;&gt;the documentation for the &lt;code&gt;UIPinchGestureRecognizer&lt;/code&gt;&lt;/a&gt; which native iOS apps use to detect pinch gestures, and which works similarly to Safari&#39;s &lt;code&gt;GestureEvent&lt;/code&gt;, emphasizes this aspect:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Take care when applying a pinch gesture recognizer’s scale factor to your content, or you might get unexpected results. Because your action method may be called many times, you cannot simply apply the current scale factor to your content. If you multiply each new scale value by the current value of your content, which has already been scaled by previous calls to your action method, your content will grow or shrink exponentially. Instead, cache the original value of your content, apply the scale factor to that original value, and apply the new value back to your content. Alternatively, reset the scale factor to 1.0 after applying each new change.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On the other hand, pinch gestures encoded as &lt;code&gt;wheel&lt;/code&gt; events contain deltas that correspond to &lt;em&gt;percentual changes&lt;/em&gt; in scale that you&#39;re supposed to apply incrementally. Negative deltas increase the object&#39;s scale, while positive deltas dicrease the object&#39;s scale. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;delta: 10&lt;/code&gt; shrinks the object by &lt;code&gt;10%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delta: -20&lt;/code&gt; enlarges the object by &lt;code&gt;20%&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A single formula covers both cases:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; previous_scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; delta &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Accumulating a series of deltas &lt;code&gt;d1&lt;/code&gt;, &lt;code&gt;d2&lt;/code&gt;, …, &lt;code&gt;dN&lt;/code&gt; into a final scaling factor requires some back-of-the-napkin arithmetics. The intermediary scales…&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; scale1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; initial_scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; d1&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; scale2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scale1 &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; d2&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; scale3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scale2 &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; d3&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// …and so on&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…lead us to the formula for the final scale:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; factor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; d1&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; d2&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; … &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; dN&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; final_scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; initial_scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; factor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lets us flesh out the &lt;code&gt;scale&lt;/code&gt; we&#39;re supposed to send to our &lt;code&gt;startGestue&lt;/code&gt;, &lt;code&gt;doGesture&lt;/code&gt; and &lt;code&gt;endGesture&lt;/code&gt; functions we introduced in the previous section:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;wheel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;normalizeWheel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ctrlKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// pinch-zoom gesture&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; factor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; factor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// pan gesture&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
				&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
				&lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; dy
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach will get us &lt;code&gt;scale&lt;/code&gt; values in the same ballpark for &lt;code&gt;WheelEvent&lt;/code&gt; and &lt;code&gt;GestureEvent&lt;/code&gt;, but you&#39;ll notice pinches in Firefox and Chrome effect a smaller scale factor than similar gestures in Safari. We can solve this by mixing in a &lt;code&gt;SPEEDUP&lt;/code&gt; multiplier that makes up for the difference:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Eyeballing it suggests the sweet spot
	for SPEEDUP is somewhere between 
	1.5 and 3. Season to taste!
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; factor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As Stephane points out over email, there&#39;s one final touch-up we could make that involves the symmetry of negative and positive &lt;code&gt;dy&lt;/code&gt; values: when you shrink an object by &lt;code&gt;20%&lt;/code&gt;, you can&#39;t bring it back to its original scale by enlarging it by &lt;code&gt;20%&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That wheel events for pinching in and out don&#39;t add up to zero is an unfortunate side-effect of how browsers &lt;a href=&quot;https://hg.mozilla.org/mozilla-central/file/b81970e39db444fa9a70eaf2e656f3e48c18a7c1/widget/InputData.cpp#l622&quot;&gt;have implemented&lt;/a&gt; the pinch-to-wheel feature. To work around the asymmetry we can adjust how we &lt;q&gt;shrink the object by N%&lt;/q&gt; to mean &lt;q&gt;shrink it by X% so that enlarging it by N% would restore the original scale&lt;/q&gt;. Solving the equation for X:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(1 - X/100) * (1 + N/100) = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…gives us the adjusted factor for positive values of &lt;code&gt;dy&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; factor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; 
	&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
	&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The listing below contains the final code for handling wheel events, let&#39;s see what we can do about touch events next.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Full listing for the wheel handling code&lt;/summary&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WHEEL_TRANSLATION_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;wheel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;normalizeWheel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ctrlKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// pinch-zoom&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; factor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; 
			&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
			&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WHEEL_SCALE_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; factor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// panning&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
				&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WHEEL_TRANSLATION_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
				&lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WHEEL_TRANSLATION_SPEEDUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dy
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Converting touch events to gestures&lt;/h3&gt;
&lt;p&gt;Touch events are more low-level. They contain everything we need to derive each component of the linear transformation ourselves. Each individual touchpoint is encoded in the &lt;code&gt;event.touches&lt;/code&gt; list as a &lt;code&gt;Touch&lt;/code&gt; object containing, among others, its coordinates &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Emitting gesture-like events&lt;/h4&gt;
&lt;p&gt;The four touch events are &lt;code&gt;touchstart&lt;/code&gt;, &lt;code&gt;touchmove&lt;/code&gt;, &lt;code&gt;touchend&lt;/code&gt; and &lt;code&gt;touchcancel&lt;/code&gt;.
We want to map these to the &lt;code&gt;startGesture&lt;/code&gt;, &lt;code&gt;doGesture&lt;/code&gt; and &lt;code&gt;endGesture&lt;/code&gt; callbacks.&lt;/p&gt;
&lt;p&gt;Each individual touch produces a &lt;code&gt;touchstart&lt;/code&gt; event on contact and a &lt;code&gt;touchend&lt;/code&gt; event when lifted from the touchscreen. The &lt;code&gt;touchcancel&lt;/code&gt; event is emitted when the browser wants to bail out of the gesture (for example, when adding too many touchpoints to the screen).&lt;/p&gt;
&lt;p&gt;For our purpose we want to observe gestures involving exactly two touchpoints, and we use the same function &lt;code&gt;watchTouches&lt;/code&gt; for all three events.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;watchTouches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; touchMove&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchcancel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; touchMove&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchcancel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchstart&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;touchmove&lt;/code&gt; event is the only one using its own separate listener:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;touchMove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cancelable &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Producing the linear transformation&lt;/h4&gt;
&lt;p&gt;The initial touches that begin a gesture must be stored so we can keep track of how they move throughout the gesture. We take advantage of the fact that &lt;code&gt;TouchList&lt;/code&gt; and &lt;code&gt;Touch&lt;/code&gt; objects are immutable and just save a reference:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; initial_touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;watchTouches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		initial_touches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		…
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	…
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The argument to &lt;code&gt;startGesture&lt;/code&gt; is straightforward: we haven&#39;t done any gesturing yet, so all parts of the transformation are set to their initial values. The origin of the transformation is the midpoint between the two initial touchpoints:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the midpoint computed as:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;touches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;t1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
		&lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;code&gt;doGesture&lt;/code&gt; function, we must compare our pair of current touchpoints to the initial ones, and using the distance and angle formed by each pair (for which functions are defined below):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;touches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;t1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hypot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; t1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; t1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;angle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;touches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;t1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; t1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientX&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; t2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; t1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;atan2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can produce the argument to &lt;code&gt;doGesture&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; mp_init &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; mp_curr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;angle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;angle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mp_curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mp_init&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
		&lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mp_curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mp_init&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mp_init
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, let&#39;s tackle the argument to &lt;code&gt;endGesture&lt;/code&gt;. It can&#39;t be computed on the spot, since at the moment when &lt;code&gt;endGesture&lt;/code&gt; gets called, we explicitly &lt;em&gt;don&#39;t&lt;/em&gt; have two touchpoints available anymore. We must keep track of the last gesture we have produced and send that to the &lt;code&gt;endGesture&lt;/code&gt; function. The &lt;code&gt;gesture&lt;/code&gt; variable, which up until this point held a boolean value, sounds like a good place to do it.&lt;/p&gt;
&lt;p&gt;Expand the section below to see how the &lt;code&gt;watchTouches&lt;/code&gt; and &lt;code&gt;touchMove&lt;/code&gt; functions look with everything put together.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Full listing for the touch events code&lt;/summary&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; initial_touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;watchTouches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		initial_touches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;touchstart&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; touchMove&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchcancel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; touchMove&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchcancel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchstart&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; watchTouches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;touchMove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; mp_init &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; mp_curr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;midpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;angle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;angle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initial_touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;translation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
				&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mp_curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mp_init&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
				&lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mp_curr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mp_init&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mp_init
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Safari mobile: touch or gesture events?&lt;/h3&gt;
&lt;p&gt;Safari mobile (iOS and iPadOS) is the only browser that has support for both &lt;code&gt;GestureEvent&lt;/code&gt; and &lt;code&gt;TouchEvent&lt;/code&gt;, so which one should we choose for handling two-finger gestures?&lt;/p&gt;
&lt;p&gt;On the one hand, enhancements Safari applies to &lt;code&gt;GestureEvent&lt;/code&gt;s makes them feel smoother; on the other hand, &lt;code&gt;TouchEvent&lt;/code&gt;s afford capturing the translation aspect of the gesture. We can&#39;t have both… can we?&lt;/p&gt;
&lt;p&gt;Surprisingly, we can. Safari&#39;s &lt;a href=&quot;https://developer.apple.com/documentation/webkitjs/touchevent&quot;&gt;implementation of &lt;code&gt;TouchEvent&lt;/code&gt;&lt;/a&gt; contains the same &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;rotation&lt;/code&gt; properties as the equivalent &lt;code&gt;GestureEvent&lt;/code&gt;, if we ever want to use the browser&#39;s, rather than our own manually computed, values.&lt;/p&gt;
&lt;p&gt;A check for the existence of the two interfaces lets us restrict gesture events to desktop Safari:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Only attach GestureEvent listeners on macOS Safari.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GestureEvent &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TouchEvent &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gesturestart&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gesturechange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gestureend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; …&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;apply-transform&quot;&gt;
	Applying the transformation
&lt;/h2&gt;
&lt;p&gt;The final piece of the puzzle has us actually applying the transformation to an element. We we talk about transforming elements on a web page, we generally mean either an HTML or a SVG element. The techniques are similar, but the two languages diverge in some aspects.&lt;/p&gt;
&lt;h3&gt;From gesture to DOMMatrix&lt;/h3&gt;
&lt;p&gt;We can express the gesture as a transformation matrix, which uses the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix/&quot;&gt;DOMMatrix&lt;/a&gt; interface:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gestureToMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;gesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rotation &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To apply the DOM matrix to an HTML element, we simply add it to the element&#39;s style on the &lt;code&gt;transform&lt;/code&gt; property. On SVG elements we can similarly set the element&#39;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform&quot;&gt;&lt;code&gt;transform&lt;/code&gt; attribute&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;applyMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; matrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; matrix&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SVGElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;transform&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; matrix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Expected HTML or SVG element&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Applying the gesture origin&lt;/h3&gt;
&lt;p&gt;The origin of the transformation for an element can be configured via the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin&quot;&gt;&lt;code&gt;transform-origin&lt;/code&gt; CSS property&lt;/a&gt; or its &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform-origin&quot;&gt;equivalent SVG attribute&lt;/a&gt;. But we can actually factor it right into our transformation. The &lt;a href=&quot;https://drafts.csswg.org/css-transforms/#transform-rendering&quot;&gt;CSS Transforms Module Level 1&lt;/a&gt; states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The transformation matrix is computed from the &lt;code&gt;transform&lt;/code&gt; and &lt;code&gt;transform-origin&lt;/code&gt; properties as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with the identity matrix.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translate by the computed X and Y of &lt;code&gt;transform-origin&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Multiply by each of the transform functions in &lt;code&gt;transform&lt;/code&gt; property from left to right.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translate by the negated computed X and Y values of &lt;code&gt;transform-origin&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can update the &lt;code&gt;gestureToMatrix()&lt;/code&gt; function to reference an &lt;code&gt;origin&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gestureToMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;translation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rotation &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may be wondering why we are sending in &lt;code&gt;origin&lt;/code&gt; as a second argument to the &lt;code&gt;gestureToMatrix()&lt;/code&gt; function instead of just reading it off the &lt;code&gt;gesture&lt;/code&gt; object. The reason for this is that the origin of the transformation is fixed at the midpoint of the &lt;em&gt;initial&lt;/em&gt; touchpoints.&lt;/p&gt;
&lt;p&gt;The origin doesn&#39;t change with each &lt;code&gt;doGesture()&lt;/code&gt;. We compute it once, at the beginning of the gesture.&lt;/p&gt;
&lt;p&gt;For HTML elements, we need to express the origin in relation to the element itself, so we read the element&#39;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect&quot;&gt;bounding client rectangle&lt;/a&gt;. On the other hand, transformations to a SVG element relate to the element&#39;s nearest &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; container, which establishes its coordinate system. The container&#39;s &lt;code&gt;getScreenCTM()&lt;/code&gt; returns the matrix that maps from its coordinate system to screen coordinates, so we use its &lt;code&gt;inverse()&lt;/code&gt; to express the transformation origin in terms of &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; coordinates.&lt;/p&gt;
&lt;p&gt;The unified &lt;code&gt;getOrigin()&lt;/code&gt; function below works for both HTML and SVG elements:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOrigin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; rect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBoundingClientRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SVGElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; matrix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ownerSVGElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScreenCTM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inverse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matrixTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matrix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Expected HTML or SVG element&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For transforms to work correctly on HTML elements, we need to jump through a couple of more hoops:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set the element&#39;s origin to the top-left corner&lt;/strong&gt; with &lt;code&gt;transform-origin: 0 0&lt;/code&gt;. The default is the object&#39;s center at &lt;code&gt;50% 50%&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clear the element&#39;s transform&lt;/strong&gt; at the beginning of each gesture, before reading its &lt;code&gt;getBoundingClientRect()&lt;/code&gt;, to obtain the origin in relation to the original, untransformed position of the element.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These concerns are factored into the final solution below.&lt;/p&gt;
&lt;h3&gt;Putting it all together&lt;/h3&gt;
&lt;p&gt;Here&#39;s the code to apply the transformation matrix to the object (either HTML or SVG) on the &lt;code&gt;startGesture&lt;/code&gt;, &lt;code&gt;doGesture&lt;/code&gt; and &lt;code&gt;endGesture&lt;/code&gt; callbacks.&lt;/p&gt;
&lt;p&gt;Notice how we hold onto the object&#39;s initial matrix in &lt;code&gt;init_m&lt;/code&gt; to multiply into the current matrix at every step of the gesture.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;
&lt;span class=&quot;token comment&quot;&gt;/*
	Older versions of Safari expose transformation matrices 
	on the `WebKitCSSMatrix` interface instead of `DOMMatrix`
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DOMMatrix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WebKitCSSMatrix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DOMMatrix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WebKitCSSMatrix&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Couldn&#39;t find a DOM Matrix implementation&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; origin&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; init_m &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; el &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#target&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
	HTML elements have their `transform-origin` 
	set to &#39;50% 50%&#39; by default, we need to 
	reset it to the top-left corner.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transformOrigin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0 0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;startGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;gesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/*
			Clear the element&#39;s transform so we can 
			measure its original position wrt. the screen.

			(We don&#39;t need to restore it because it gets 
			overwritten by `applyMatrix()` anyways.)
		 */&lt;/span&gt;
		el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	origin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOrigin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;applyMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gestureToMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; origin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;init_m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;gesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;applyMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gestureToMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; origin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;init_m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;endGesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;gesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	init_m &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gestureToMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; origin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;init_m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;applyMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; init_m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test this code out in these demos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/demo-html.html&quot;&gt;Demo: Gestures on an HTML element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/demo-svg.html&quot;&gt;Demo: Gestures on a SVG element&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;further-refinements&quot;&gt;Refining the implementation&lt;/h2&gt;
&lt;p&gt;Since browsers on touchscreen-enabled devices implement several default behaviors for user gestures, any tap on the screen is under scrutiny: will it be part of a double-tap? does the user want to initiate a scroll, or follow the hyperlink underneath the touchpoint?&lt;/p&gt;
&lt;p&gt;Disabling some of these default behaviors disambiguates taps. If we don&#39;t need double-tap-to-zoom gesture, the browser can make taps work like clicks without introducing artificial delays.&lt;/p&gt;
&lt;p&gt;This is the general idea behind &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action&quot;&gt;the &lt;code&gt;touch-action&lt;/code&gt; CSS property&lt;/a&gt;. It instructs the browser to only apply &lt;em&gt;some&lt;/em&gt; of its default behaviors when the user peforms one- or two-finger gestures: one-finger scrolling, pinch-zooming, or the &lt;em&gt;double-tap to zoom&lt;/em&gt; behavior.&lt;/p&gt;
&lt;p&gt;Depending on the exact needs of our implementation, we might apply &lt;code&gt;touch-action: pan-x pan-y&lt;/code&gt; (which tells the browser to apply one-finger scrolling while ignoring all other gestures), or even &lt;code&gt;touch-action: none&lt;/code&gt; to our container, in which case the responsibility is ours to implement all touch interactions. As a rule of thumb for &lt;code&gt;touch-action&lt;/code&gt;, only disable the browser behaviors you plan to replace with custom gestures.&lt;/p&gt;
&lt;p&gt;Some applications may benefit from a bit more nuance. For example, as gestures tend to be imprecise, we might impose thresholds that the various components of the transformation must reach to be considered intentional. Alternatively, we might tease out, and apply, only the most prominent component of the transformation, while ignoring the rest.&lt;/p&gt;
&lt;p&gt;I hope this article has made the basic approach clear enough that these refinements can be neatly layered on top of it. You may find some pointers in the &lt;a href=&quot;https://danburzo.ro/dom-gestures/#questions-answers&quot;&gt;Questions &amp;amp; Answers section&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this article we&#39;ve looked at how we can treat DOM &lt;code&gt;GestureEvent&lt;/code&gt;, &lt;code&gt;WheelEvent&lt;/code&gt;, and &lt;code&gt;TouchEvent&lt;/code&gt; uniformly to add support for two-finger gestures to web pages with some pretty good results across a variety of devices.&lt;/p&gt;
&lt;p&gt;Some quick links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/logger.html&quot;&gt;DOM gesture logger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/lib.js&quot;&gt;Reference JavaScript implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/demo-html.html&quot;&gt;Demo: Gestures on an HTML element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/demo-svg.html&quot;&gt;Demo: Gestures on a SVG element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/demo-buttons.html&quot;&gt;Demo: Combined gestures and zoom buttons on an HTML element&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This supplemental material is also published on GitHub in the &lt;a href=&quot;https://github.com/danburzo/ok-zoomer&quot;&gt;&lt;code&gt;danburzo/ok-zoomer&lt;/code&gt;&lt;/a&gt; repository.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;questions-answers&quot;&gt;Questions &amp; Answers&lt;/h2&gt;
&lt;h3 id=&quot;current-scale&quot;&gt;How do I find the element&#39;s current scale?&lt;/h3&gt;
&lt;p&gt;To know the element&#39;s scale at any given moment, we need to either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keep track of all the gestures we&#39;ve applied and accumulate the scales in a variable, or&lt;/li&gt;
&lt;li&gt;somehow extract the scale from the element&#39;s current transformation matrix.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix&quot;&gt;DOMMatrix&lt;/a&gt; interface doesn&#39;t have a method to retrieve individual components such as scale, translation, or rotation from a matrix, so we have to implement it ourselves.&lt;/p&gt;
&lt;p&gt;To that end we can use the algorithm described in Spencer W. Thomas&#39;s article &lt;a href=&quot;https://doi.org/10.1016/B978-0-08-050754-5.50069-4&quot;&gt;Decomposing a matrix into simple transformations&lt;/a&gt;, published in &lt;em&gt;Graphics Gems II&lt;/em&gt; (1991). A pseudo-code version is published in the &lt;a href=&quot;https://www.w3.org/TR/css-transforms-1/#decomposing-a-2d-matrix&quot;&gt;CSS Transforms specification&lt;/a&gt;, and a C implementation is &lt;a href=&quot;https://github.com/erich666/GraphicsGems/blob/master/gemsii/unmatrix.c&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The algorithm handles cases that don&#39;t apply to our situation — non-uniform scaling, skewing, etc. — so if we&#39;re only interested in extracting the uniform scale, we can get away with a one-liner:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getScaleFromMatrix&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;matrix&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hypot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matrix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; matrix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you &lt;em&gt;are&lt;/em&gt; interested in the general method, you can find some &lt;a href=&quot;https://github.com/search?q=unmatrix&quot;&gt;JavaScript implementations on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Knowing the current scale is useful in a variety of cases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zooming to a specific scale.&lt;/strong&gt; To produce an absolute scale, such as &lt;code&gt;50%&lt;/code&gt; or &lt;code&gt;400%&lt;/code&gt;, we can express it as relative to the current scale and create a gesture for it:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; current_scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getScaleFromMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current_transform_matrix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gesture &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; current_scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 50% scale&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// apply gesture&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Limiting the zoom level to a minimum/maximum.&lt;/strong&gt; Similarly to zooming to an absolute scale, we can clamp our scale to a minimum and maximum. The function below limits the scale in a gesture so that the resulting scale is in the 25–400% interval:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clampScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;gesture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; s &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getScaleFromMatrix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; proposed_scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proposed_scale &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; proposed_scale &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;gesture&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; proposed_scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; s
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; gesture&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see both these examples in action in &lt;a href=&quot;https://danburzo.ro/demos/dom-gestures/demo-buttons.html&quot;&gt;Demo: Combined gestures and zoom buttons on an HTML element&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;revisions&quot;&gt;Revisions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Nov 22, 2020:&lt;/strong&gt; Published a first draft of this article &lt;a href=&quot;https://dev.to/danburzo/pinch-me-i-m-zooming-gestures-in-the-dom-a0e&quot;&gt;on dev.to&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nov 15, 2021:&lt;/strong&gt; Updated the &lt;a href=&quot;https://danburzo.ro/dom-gestures/#handle-gesture-events&quot;&gt;Safari gesture events section&lt;/a&gt; to note that Safari 15 now also produces &lt;code&gt;wheel&lt;/code&gt; events for pinch-zoom gestures, in line with the other major browsers, as well as to point out the double-&lt;code&gt;gestureend&lt;/code&gt; bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jan 10, 2022:&lt;/strong&gt; Fixed some typos, improved wheel-to-gesture code, and added a &lt;em&gt;Questions &amp;amp; Answers&lt;/em&gt; section.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;May 19, 2022:&lt;/strong&gt; Fixed the SVG case in &lt;code&gt;applyMatrix()&lt;/code&gt; for Chrome, which doesn&#39;t currently support a &lt;code&gt;DOMMatrix&lt;/code&gt; argument to &lt;code&gt;SVGTransformList.createSVGTransformFromMatrix()&lt;/code&gt;. (&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=835431&quot;&gt;Chromium#835431&lt;/a&gt;)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Thanks to Arne, Stephane and Sam for helping improve this article.&lt;/em&gt;&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>On some features in Safari 15</title>
		<link href="https://danburzo.ro/on-safari-15/" />
		<updated>2021-09-29T00:00:00Z</updated>
		<id>https://danburzo.ro/on-safari-15/</id>
		<content type="html"
			>&lt;p&gt;A new major version of Safari was pitched back in June at the Apple WWDC 2021 event. In the &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10029/&quot;&gt;Design for Safari 15&lt;/a&gt; (33min video, transcript available), Jen Simmons and Myles C. Maxfield walk us through some of the biggest changes we could expect for the web platform.&lt;/p&gt;
&lt;p&gt;Last week Safari 15 was officially released as part of iOS/iPadOS 15. It was also included in a recent update to macOS Big Sur, although the &lt;a href=&quot;https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes&quot;&gt;release notes&lt;/a&gt; hint that it was initially planned for macOS Monterey.&lt;/p&gt;
&lt;p&gt;Now that I&#39;ve had the chance to play around with the new version, here are some personal highlights from the release notes.&lt;/p&gt;
&lt;h2&gt;More ways to specify colors&lt;/h2&gt;
&lt;p&gt;Safari 15 is the first browser to support the new color syntaxes defined in &lt;a href=&quot;https://drafts.csswg.org/css-color/&quot;&gt;the CSS Color Level 4 spec&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;HSL, and its close relative HSV, rework the RGB color model (basically &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Hsl-and-hsv.svg&quot;&gt;squishing the RGB cube into a cylinder&lt;/a&gt;) to separate colors into more intuitive dimensions. For a long time, &lt;code&gt;hsl()&lt;/code&gt; has been the only alternative to &lt;code&gt;rgb()&lt;/code&gt; for specifying colors, and is unquestionably an improvement: it&#39;s &lt;em&gt;very&lt;/em&gt; hard to imagine a RGB color by looking at the R/G/B components, and it&#39;s impractical for picking colors. By comparison, you can sort of imagine the attributes of a color just by reading its H/S/L components — what hue it has and how colorful, and light, it is — and it&#39;s easier to match a target color by adjusting Hue, Saturation, and Lightness sliders. (You can try this &lt;a href=&quot;https://michaelbach.de/ot/col-match/index.html&quot;&gt;old-school color matching game&lt;/a&gt; in RGB vs. HSL mode to see the difference.)&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://alvyray.com/Papers/CG/HWB_JGTv208.pdf&quot;&gt;HWB color space&lt;/a&gt; makes picking colors even more intuitive: provide a hue, and optionally mix in some white and some black to obtain a color. In CSS it&#39;s available under the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb()&quot;&gt;&lt;code&gt;hwb()&lt;/code&gt;&lt;/a&gt; syntax.&lt;/p&gt;
&lt;p&gt;HSL, HSV, and HWB all use attributes that are easier to reason about, but they&#39;re not really aligned to &lt;em&gt;how&lt;/em&gt; we perceive colors. &lt;a href=&quot;https://wildbit.com/blog/2021/09/16/accessible-palette-stop-using-hsl-for-color-systems&quot;&gt;HSL has its drawbacks&lt;/a&gt; and &lt;a href=&quot;https://www.vis4.net/blog/2011/12/avoid-equidistant-hsv-colors/&quot;&gt;so does HSV&lt;/a&gt; and, by extension, so does HWB. The &lt;a href=&quot;https://en.wikipedia.org/wiki/CIELAB_color_space&quot;&gt;CIELAB color space&lt;/a&gt; is much more perceptually uniform and now we can use it in CSS, with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab()&quot;&gt;&lt;code&gt;lab()&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch()&quot;&gt;&lt;code&gt;lch()&lt;/code&gt;&lt;/a&gt; forms.&lt;/p&gt;
&lt;p&gt;Finally, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color()&quot;&gt;the &lt;code&gt;color()&lt;/code&gt; function notation&lt;/a&gt;, which debuted in Safari 10.1 with support for the &lt;code&gt;srgb&lt;/code&gt; and &lt;code&gt;display-p3&lt;/code&gt; predefined color spaces, is supplemented in Safari 15 with a few more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a98-rgb&lt;/code&gt;, the compatible-with but not, &lt;a href=&quot;https://www.adobe.com/digitalimag/adobergb.html&quot;&gt;legally speaking&lt;/a&gt;, the actual &lt;a href=&quot;https://en.wikipedia.org/wiki/Adobe_RGB_color_space&quot;&gt;Adobe RGB (1998)&lt;/a&gt; color space;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prophoto-rgb&lt;/code&gt;, the &lt;a href=&quot;https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space&quot;&gt;ProPhoto RGB&lt;/a&gt; color space;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rec2020&lt;/code&gt;, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Rec._2020&quot;&gt;Rec. 2020&lt;/a&gt; color space;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xyz&lt;/code&gt;, the &lt;a href=&quot;https://en.wikipedia.org/wiki/CIE_1931_color_space&quot;&gt;CIE XYZ color space&lt;/a&gt; with a &lt;a href=&quot;https://en.wikipedia.org/wiki/Standard_illuminant&quot;&gt;D50 white point&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A redesigned color picker&lt;/h2&gt;
&lt;p&gt;Safari 15 on mobile comes with redesigned form controls, the most spectacular of which is the color picker. It now includes three separate selection modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the swatches panel from the previous version;&lt;/li&gt;
&lt;li&gt;a hue/lightness spectrum for picking fully-saturated colors;&lt;/li&gt;
&lt;li&gt;red/green/blue sliders and a hex input.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
	&lt;a href=&quot;https://danburzo.ro/img/safari-15/safari-15-ios-color-picker.png&quot;&gt;&lt;img class=&quot;cover&quot; src=&quot;https://danburzo.ro/img/safari-15/safari-15-ios-color-picker.png&quot; width=&quot;3457&quot; height=&quot;1557&quot; /&gt;&lt;/a&gt;
	&lt;figcaption&gt;The three panels in the mobile Safari 15 color picker.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Other features include the ability to save colors for reuse, and an eyedropper to sample colors from the screen. Sampling a color switches the hex input to &lt;code&gt;display-p3&lt;/code&gt;, which sort of makes sense (after all, the screen is able to render &lt;code&gt;display-p3&lt;/code&gt; colors) but on the other hand there&#39;s no concept of &lt;strong&gt;&lt;code&gt;display-p3&lt;/code&gt; hex code&lt;/strong&gt; in CSS, which may confuse folks.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;&amp;lt;input type=&#39;color&#39;&amp;gt;&lt;/code&gt; only supports six-digit hex values, an opacity slider is not included in the picker &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/ios/controls/color-wells/&quot;&gt;like in native apps&lt;/a&gt;. On the other hand, one cool feature &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/macos/selectors/color-wells/&quot;&gt;adopted from macOS&lt;/a&gt; is the ability to drag &amp;amp; drop one color well atop another to apply the value:&lt;/p&gt;
&lt;video loop=&quot;&quot; controls=&quot;&quot; width=&quot;246&quot;&gt;
	&lt;source src=&quot;https://danburzo.ro/img/safari-15/safari-15-ios-color-wells.mp4&quot; /&gt;
&lt;/video&gt;
&lt;h2 id=&quot;pull-to-refresh&quot;&gt;A built-in pull-to-refresh gesture&lt;/h2&gt;
&lt;p&gt;Safari 15 introduces a built-in pull-to-refresh gesture on mobile that&#39;s pretty handy.&lt;/p&gt;
&lt;p&gt;I see it as especially beneficial to progressive web apps, where you don&#39;t have a way to refresh the page unless you implement a custom action. However, at the moment of writing, the gesture does not carry over to Progressive Web Apps.&lt;/p&gt;
&lt;p&gt;It also seems that you can&#39;t disable the gesture using the &lt;a href=&quot;https://danburzo.ro/css-overscroll-behavior/&quot;&gt;&lt;code&gt;overscroll-behavior&lt;/code&gt;&lt;/a&gt; CSS property, as it&#39;s not currently implemented (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=176454&quot;&gt;WebKit#176454&lt;/a&gt;). Websites that implement their own similar gesture may suffer from a jarring double-refresh effect. If that&#39;s the case, the only course of action is to disable the custom behavior. Since &lt;code&gt;overscroll-behavior-y&lt;/code&gt; is &lt;a href=&quot;https://developers.google.com/web/updates/2017/11/overscroll-behavior&quot;&gt;the most likely vehicle&lt;/a&gt; for preventing the built-in gesture when it becomes possible, you can use this JavaScript snippet to target problematic browser versions:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Feature-detect Safari versions 
	with an unpreventable pull-to-refresh gesture.

	How it works: 

	Safari 15 introduced support for the `xyz` color space,
	while simultaneously being the last major browser engine 
	lacking support for `overscroll-behavior`.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isSafariWithUnstoppablePullRefresh &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
	window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CSS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
	&lt;span class=&quot;token constant&quot;&gt;CSS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;supports &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
	&lt;span class=&quot;token constant&quot;&gt;CSS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;supports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(color: color(xyz))&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
	&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CSS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;supports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(overscroll-behavior-y: contain)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
	window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TouchEvent &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
	&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;standalone&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isSafariWithUnstoppablePullRefresh&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// initialize custom pull-to-refresh gesture&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The &lt;code&gt;theme-color&lt;/code&gt; HTML meta tag&lt;/h2&gt;
&lt;p&gt;Across platforms, Safari 15 is using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color&quot;&gt;the &lt;code&gt;theme-color&lt;/code&gt; meta tag&lt;/a&gt; to tint the browser UI.&lt;/p&gt;
&lt;p&gt;What I found surprising is the feature is not opt-in. In the absence of this meta tag, Safari will try to infer a tint for the top bar, and the color it chooses may not always be appropriate. It may be useful to include this meta tag to your HTML boilerplate, along with the &lt;a href=&quot;https://htmlhead.dev/&quot;&gt;other things that go in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;&lt;/a&gt;, to avoid weird color combos.&lt;/p&gt;
&lt;p&gt;Manuel Matuzović explores what you can and can&#39;t do with &lt;code&gt;theme-color&lt;/code&gt; in &lt;a href=&quot;https://css-tricks.com/meta-theme-color-and-trickery/&quot;&gt;this CSS-Tricks article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;new-tab-bar&quot;&gt;A new tab bar&lt;/h2&gt;
&lt;p&gt;Safari debuted with a radical interface redesign that merged the old address bar and the navigation bar from previous versions into a single, floating URL bar at the bottom of the screen.&lt;/p&gt;
&lt;p&gt;The change was met with both enthusiasm and reluctance. One concern with the floating tab bar was that it may stand in the way of things on the page. It was acknowledged and planned for: a bullet point under the CSS section in the release notes reads &lt;q&gt;adjusted environment variable calculations where appropriate to adjust for the safe area of the new iOS design&lt;/q&gt;. As &lt;a href=&quot;https://lukechannings.com/blog/2021-06-09-does-safari-15-fix-the-vh-bug/&quot;&gt;documented by Luke Channings&lt;/a&gt;, Safari 15 Beta did afford a combination of &lt;code&gt;height: 100vh&lt;/code&gt; and &lt;code&gt;env(safe-area-inset-bottom)&lt;/code&gt; to place a toolbar at the bottom of the screen without risking it being hidden behind the URL bar.&lt;/p&gt;
&lt;p&gt;The final Safari 15 release back-pedalled into more familiar ground, with a tamer combined bottom bar, and an option to bring back the old top-anchored address bar. After a week of using it, the new UI seems to have mostly faded out of attention, and I appreciate the ease of switching tabs with a horizontal swipe. And although the &lt;span class=&quot;ui&quot;&gt;Aa&lt;/span&gt; menu is becoming a bit of a kitchen sink cabinet, all in all I feel like this is a step in the right direction.&lt;/p&gt;
&lt;p&gt;What about the changes to &lt;code&gt;env()&lt;/code&gt;? As seen &lt;a href=&quot;https://danburzo.ro/demos/100vh-and-env.html&quot;&gt;in this demo&lt;/a&gt;, they seem to have been reverted for the case we were interested in: &lt;code&gt;env(safe-area-inset-bottom)&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; when floating toolbar is shown, and it still obstructs the bottom part of the page.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For reference:&lt;/strong&gt; Here&#39;s &lt;a href=&quot;https://danburzo.ro/reference/browser-tests/env/&quot;&gt;a browser test&lt;/a&gt; that shows the computed value of available &lt;code&gt;env()&lt;/code&gt; properties, as well as &lt;code&gt;100vh&lt;/code&gt; and the &lt;code&gt;-webkit-fill-available&lt;/code&gt; height.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The way out seems to be the &lt;a href=&quot;https://drafts.csswg.org/css-values-4/#viewport-relative-lengths&quot;&gt;new viewport units&lt;/a&gt;, in particular &lt;code&gt;dvh&lt;/code&gt; (dynamic viewport height). Bramus has &lt;a href=&quot;https://www.bram.us/2021/07/08/the-large-small-and-dynamic-viewports/&quot;&gt;an explainer&lt;/a&gt; on these units. (&lt;strong&gt;Nov 24, 2021 update:&lt;/strong&gt; Safari TP 135 has just &lt;a href=&quot;https://webkit.org/blog/12040/release-notes-for-safari-technology-preview-135/&quot;&gt;added support for them&lt;/a&gt;).&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>How it&#39;s made: Watch/Star/Fork</title>
		<link href="https://danburzo.ro/wsf-making-of/" />
		<updated>2021-09-12T09:01:00Z</updated>
		<id>https://danburzo.ro/wsf-making-of/</id>
		<content type="html"
			>&lt;p&gt;I thought I&#39;d write a bit about my awkward but workable flow for adding several dozens of links to each &lt;a href=&quot;https://danburzo.ro/watchstarfork/&quot;&gt;Watch/Star/Fork&lt;/a&gt; edition, and my usage of bookmarks in general.&lt;/p&gt;
&lt;h2&gt;From many inboxes to one&lt;/h2&gt;
&lt;p&gt;So, out of the two available types of people, I am a bookmark person. The only time I have more than a dozen tabs open is while I research something, and even that dissipates as quickly as I ⌘-clicked my way there.&lt;/p&gt;
&lt;p&gt;Firefox has the best ergonomics for bookmark people, so that&#39;s where I hang out most. Whenever I see something interesting and/or potentially useful, I drag &amp;amp; drop the tab onto my &lt;a href=&quot;https://support.mozilla.org/en-US/kb/bookmarks-toolbar-display-favorite-websites&quot;&gt;bookmarks toolbar&lt;/a&gt;, which has little bins for sorting everything neatly. If it&#39;s something I feel belongs here, I drop it in the &lt;code&gt;wsf&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Before, if an online service had some sort of star/like/favorite/upvote function that gets saved and attached to my profile, I&#39;d use it to bookmark things I wanted to get back to later. But with so many inboxes, I&#39;d rarely actually revisit them, so I&#39;ve started to direct everything towards actual bookmarks.&lt;/p&gt;
&lt;p&gt;To get the data out of the various places in the &lt;a href=&quot;https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753582(v=vs.85)&quot;&gt;weird de facto standard bookmark format&lt;/a&gt;, I employed a variety of tools, some of which — &lt;a href=&quot;https://github.com/danburzo/hred&quot;&gt;hred&lt;/a&gt; and &lt;a href=&quot;https://github.com/danburzo/nbf&quot;&gt;nbf&lt;/a&gt; — I wrote for the purpose. The &lt;a href=&quot;https://github.com/danburzo/nbf&quot;&gt;nbf repository page&lt;/a&gt; collects a few recipes for things like GitHub stars or NetNewsWire favorites.&lt;/p&gt;
&lt;p&gt;As a grand final gesture I tried removing my historical &amp;quot;bookmarks&amp;quot; from these services, with whichever method seemed quicker. I mostly succeeded, save for a few minor incidents involving GitHub stars (where a kind person from GitHub support fixed a lingering star count) and unreachable social media likes. Oops!&lt;/p&gt;
&lt;p&gt;I now use temporary social media likes to show my interest &amp;amp; appreciation, but once a week I&#39;ll go through them, bookmark any useful links, and then reset everything. (I know there&#39;s the separate &lt;em&gt;Bookmarks&lt;/em&gt; feature, but I prefer to send a little signal of gratitude.) And with the new system in place, my &lt;a href=&quot;https://github.com/danburzo?tab=stars&quot;&gt;GitHub stars page&lt;/a&gt; is always empty.&lt;/p&gt;
&lt;h2&gt;Safari&#39;s Reading List as syncing tool&lt;/h2&gt;
&lt;p&gt;To sync bookmarks between my phone and my laptop I use, out of all the possible technology available in the year 2021, Safari&#39;s Reading List. A bit like in this funny, insightful tweet I saw on my timeline today:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;my toxic designer trait is using the rectangle tool to make boxes in order to measure spacing between objects instead of using any sort of built-in measuring/ruler/guide tool the hardworking developers have made for me — @sievish on Sep 10, 2021&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The rationale is simple: all apps on my phone that deal with web pages (Safari, NetNewsWire, Are.na, etc.) have access, directly or indirectly, to the &lt;samp&gt;Add to reading list&lt;/samp&gt; action from the iOS sharing panel, so it&#39;s just a convenient, mostly reliable way to add things to a single inbox that then shows up on my laptop without me needing to configure or install anything extra.&lt;/p&gt;
&lt;p&gt;One snag I have with this setup is that Safari sometimes decides, out of the blue and without indication, to stop saving web pages to the Reading List. I haven&#39;t traced the reason behind it, and it&#39;s been an issue for a number of years now, but it usually fixes itself in a couple of days (not exactly sure how), and I&#39;m not too precious about the odd bookmark going missing here and there. But just so you know.&lt;/p&gt;
&lt;p&gt;From macOS Safari to Firefox is a short trip: nowadays Safari provides an &lt;samp&gt;Export bookmarks&lt;/samp&gt; function that includes the Reading List, and before that I used to maintain &lt;a href=&quot;https://github.com/danburzo/export-safari-reading-list&quot;&gt;a little tool&lt;/a&gt; to extract the bookmarks from Safari&#39;s &lt;code&gt;Bookmarks.plist&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;From bookmark to Markdown&lt;/h2&gt;
&lt;p&gt;The last leg of the journey from browser tabs to blog post is turning the &lt;code&gt;wsf&lt;/code&gt; bookmarks folder into the Markdown that powers this site.&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, Firefox has great affordances around bookmarks. One of them is being able to &lt;samp&gt;Right-click → Copy&lt;/samp&gt; or drag &amp;amp; drop entire folders. Pasting a folder into &lt;a href=&quot;https://evercoder.github.io/clipboard-inspector/&quot;&gt;Clipboard inspector&lt;/a&gt;, it transpires that Firefox puts into the clipboard, as the &lt;code&gt;text/html&lt;/code&gt; entry, actual, honest-to-goodness HTML (in the Netscape Bookmark Format), similar to what you&#39;d get if you used the &lt;em&gt;Export bookmarks to HTML&lt;/em&gt; function.&lt;/p&gt;
&lt;p&gt;On the other hand, there several online one-page tools to convert HTML to Markdown; I&#39;ve settled on &lt;a href=&quot;https://mixmark-io.github.io/turndown/&quot;&gt;Turndown&lt;/a&gt;, which works nicely with the default settings. There&#39;s a small hoop to jump through: when you paste a Firefox folder into Turndown&#39;s &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; element, it gets the &lt;code&gt;text/plain&lt;/code&gt; clipboard entry. I&#39;ve submitted &lt;a href=&quot;https://github.com/mixmark-io/turndown/pull/399&quot;&gt;a pull request&lt;/a&gt; to grab the &lt;code&gt;text/html&lt;/code&gt; data when it exists, but in the meantime I use Clipboard Inspector&#39;s &lt;em&gt;Copy as plain text&lt;/em&gt; function.&lt;/p&gt;
&lt;p&gt;One thing I still do by hand is add the authors&#39; names next to each link, but I&#39;m glad I get at least a head start on all of the title/link pairs.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Use code to explore and change JavaScript files</title>
		<link href="https://danburzo.ro/javascript-codemods/" />
		<updated>2021-09-08T00:00:00Z</updated>
		<id>https://danburzo.ro/javascript-codemods/</id>
		<content type="html"
			>&lt;p&gt;A while back I wrote, as an exercise in minimalism, the &lt;a href=&quot;https://github.com/danburzo/nano-i18n&quot;&gt;&lt;code&gt;nano-i18n&lt;/code&gt;&lt;/a&gt; library for localizing strings. You write internationalized strings with a template tag:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;nano-i18n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dan&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hello, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also provides a hook for missing translations, so you can log them to the browser console, or collect them in one place so that they&#39;re easier to find when you want to translate them. Pretty straightforward stuff.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; config &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;nano-i18n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function-variable function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;msg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Missing: &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;key&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dan&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hello, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// =&gt; Missing: &quot;Hello, {}!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way of collecting strings has its disadvantages. Someone needs to go through all the possible states of the interface to trigger aforementioned hook. Worse, when you redo bits of the interface, you need the presence of mind to remember to clean up the strings that are no longer in use.&lt;/p&gt;
&lt;p&gt;Instead, let&#39;s see how we can automate our way out of this predicament, and extract from a JavaScript codebase all string literals tagged with the &lt;code&gt;t&lt;/code&gt; template with a script.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;For answering basic questions about a codebase, or for simple code alterations, I would normally first attempt a well-crafted regular expression. I&#39;ll paste it into Sublime Text&#39;s &lt;em&gt;Search &amp;amp; Replace&lt;/em&gt; interface, or &lt;a href=&quot;https://github.com/BurntSushi/ripgrep&quot;&gt;ripgrep&lt;/a&gt; (look at the &lt;code&gt;--only-matching&lt;/code&gt; and &lt;code&gt;--replace&lt;/code&gt; options), or some combination standard command-line tools I&#39;ll never remember unless &lt;a href=&quot;https://danburzo.ro/toolbox/unix-cli&quot;&gt;I have it written down somewhere&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is not one of those times. The &lt;code&gt;t&lt;/code&gt; tag can interpolate basically any JavaScript expression within the translation, so it&#39;s impossible to deploy a regex to match all the patterns that might occur in the typical codebase. We need a tool that &lt;em&gt;understands&lt;/em&gt; the syntax we&#39;re working with.&lt;/p&gt;
&lt;h2&gt;A short survey of the tools available&lt;/h2&gt;
&lt;p&gt;There are quite a few options for parsing JavaScript: there&#39;s &lt;a href=&quot;https://github.com/jquery/esprima&quot;&gt;&lt;code&gt;esprima&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/acornjs/acorn&quot;&gt;&lt;code&gt;acorn&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://babeljs.io/docs/en/babel-parser.html&quot;&gt;&lt;code&gt;@babel/parser&lt;/code&gt;&lt;/a&gt;. These parsers produce an Abstract Syntax Tree (AST) from a JavaScript string, either adhering the &lt;a href=&quot;https://github.com/estree/estree&quot;&gt;&lt;code&gt;ESTree&lt;/code&gt;&lt;/a&gt; format, or to something fairly similar.&lt;/p&gt;
&lt;p&gt;Further along, &lt;a href=&quot;https://github.com/benjamn/recast&quot;&gt;&lt;code&gt;recast&lt;/code&gt;&lt;/a&gt; helps you alter the AST, and then remake it into a string. A combination of &lt;a href=&quot;https://github.com/davidbonnet/astring&quot;&gt;&lt;code&gt;astring&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/davidbonnet/astravel&quot;&gt;&lt;code&gt;astravel&lt;/code&gt;&lt;/a&gt; serve a similar purpose. But how do you know &lt;em&gt;how&lt;/em&gt; to alter the AST? &lt;a href=&quot;https://astexplorer.net/&quot;&gt;AST Explorer&lt;/a&gt; lets you type in sample JavaScript code and shows you the resulting tree as an interactive, navigable JSON.&lt;/p&gt;
&lt;p&gt;Finally, &lt;a href=&quot;https://github.com/facebook/jscodeshift&quot;&gt;&lt;code&gt;jscodeshift&lt;/code&gt;&lt;/a&gt; is a command-line tool written in JavaScript that uses &lt;code&gt;recast&lt;/code&gt; and helps with bits of admin, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;picking up on your Babel configuration if you have one; this is handy if you&#39;re using plugins for more exotic syntax.&lt;/li&gt;
&lt;li&gt;running things in parallel to speed up processing a large codebase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s used for a variety of purposes, such as &lt;a href=&quot;https://github.com/reactjs/react-codemod&quot;&gt;keeping your React components up to date&lt;/a&gt; with the occasional changes in React&#39;s API.&lt;/p&gt;
&lt;p&gt;You could start with the tools from any level on the abstraction ladder, and build up from there. In this particular case, I&#39;d rather not get bogged down in unrelated complexities &amp;amp; gotchas and focus on the unique aspects of the task at hand. With a few tweaks here and there, we can adapt &lt;code&gt;jscodeshift&lt;/code&gt; to not modify JavaScript files, but instead gather statistics on them, so that&#39;s going to be the tool of choice today.&lt;/p&gt;
&lt;h2&gt;Analyzing code with &lt;code&gt;jscodeshift&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Install &lt;code&gt;jscodeshift&lt;/code&gt; from npm and add a script to your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;gather&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node node_modules/.bin/jscodeshift --help&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the script with &lt;code&gt;npm run gather&lt;/code&gt; just displays its help information for now, but I wanted to get out of the way the non-standard way of invoking it. You don&#39;t usually need to run Node.js CLI tools with &lt;code&gt;node&lt;/code&gt;, but this form is necessary at the time of writing to work around &lt;a href=&quot;https://github.com/facebook/jscodeshift/issues/424&quot;&gt;a quirk in &lt;code&gt;jscodeshift&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To actually do something, the tool takes a JavaScript file containing the transform we want to perform on our JavaScript files, under the &lt;code&gt;--transform&lt;/code&gt; command-line argument. It also needs a folder in which to look for files, so a proper invocation looks like this (with the &lt;code&gt;node&lt;/code&gt; part omitted):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;jscodeshift &lt;span class=&quot;token parameter variable&quot;&gt;--transform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;gather-translatables.js js/&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A word of caution:&lt;/strong&gt; &lt;code&gt;jscodeshift&lt;/code&gt; has the ability to overwrite your JavaScript files. Make sure you use a version control system such as Git on the codebase you&#39;re working with, and that you don&#39;t have any pending changes to commit, before messing around with transforms.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The transform is listed below. The API looks a bit like jQuery, with chained methods on a collection of AST nodes.&lt;/p&gt;
&lt;p&gt;Technically this is the point where you&#39;d modify the AST to write back to the file, the task at which &lt;code&gt;jscodeshift&lt;/code&gt; excels. We only want to gather some statistics, so we &lt;strong&gt;don&#39;t return anything&lt;/strong&gt; from the transform function — this instructs &lt;code&gt;jscodeshift&lt;/code&gt; to keep the source files intact.&lt;/p&gt;
&lt;p&gt;Instead we use the &lt;code&gt;report&lt;/code&gt; function that writes its argument to the console output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;gather-translatables.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	The jscodeshift transform
	-------------------------
 */&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fileInfo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; jscodeshift&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;jscodeshift&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jscodeshift&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaggedTemplateExpression&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isTranslationTag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toTranslationKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;n&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
	Matches template literals tagged with &quot;t&quot;:

		t`Hello, world!`
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isTranslationTag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Identifier&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;t&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
	Converts a tagged template literal 
	to a `nano-i18n` translation key:

		t`Hello, ${stranger}!`

	Becomes:

		&#39;Hello, ${0}!&#39;
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toTranslationKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; quasis &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quasi&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; quasis
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;q&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; idx&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tail &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;idx&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To see what I need to match and how to process the AST in the transform function, I&#39;ve looked at the structure of a typical tagged template literal in &lt;a href=&quot;https://astexplorer.net/&quot;&gt;AST Explorer&lt;/a&gt;, with this sample JavaScript code:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;t&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hello, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;stranger&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
	&lt;summary&gt;AST Explorer output&lt;/summary&gt;
&lt;p&gt;This is a simplified JSON output with just the essential properties for each node.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;TaggedTemplateExpression&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;tag&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Identifier&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;t&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;quasi&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;TemplateLiteral&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;expressions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Identifier&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;stranger&quot;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;quasis&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;TemplateElement&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token property&quot;&gt;&quot;raw&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;token property&quot;&gt;&quot;cooked&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, &quot;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;tail&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;TemplateElement&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token property&quot;&gt;&quot;raw&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;token property&quot;&gt;&quot;cooked&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;!&quot;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token property&quot;&gt;&quot;tail&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;The transform does what we want it to do — it extracts all the translation keys used in any JavaScript file from the the &lt;code&gt;js/&lt;/code&gt; folder. The AST processing part is done.&lt;/p&gt;
&lt;p&gt;Let&#39;s turn our attention to how to shape this raw data we&#39;ve just produced into something usable. Invoking the &lt;code&gt;gather&lt;/code&gt; script, we can see that the output of &lt;code&gt;report()&lt;/code&gt; is peppered with miscellaneous output from the tool, some of it colored. To clean up the output, we can use &lt;code&gt;jscodeshift&lt;/code&gt;&#39;s &lt;code&gt;--no-color&lt;/code&gt; and &lt;code&gt;--silent&lt;/code&gt; flags, which suppress the color and the informational output, respectively.&lt;/p&gt;
&lt;details&gt;
	&lt;summary&gt;Updated &lt;code&gt;gather&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt;&lt;/summary&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;gather&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node node_modules/.bin/jscodeshift --no-color --silent --transform=gather-translatables.js js/&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;Running the script again produces a cleaner output as a series of items with the following structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; REP js/path/to/hello.js
Hello, ${0}!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This form is not ideal, but the data is good enough to pipe into a separate script to achieve its final form. Here&#39;s a small Node.js script that does two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the first part &#39;slurps&#39; all the content from &lt;code&gt;stdin&lt;/code&gt; (Sindre Sorhus has it packaged as &lt;a href=&quot;https://github.com/sindresorhus/get-stdin&quot;&gt;get-stdin&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;that content is then split into a flat list of unique, sorted translation keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;sort-gathered.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
stdin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
	Accumulate the input from `stdin`
	into the `content` string.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
stdin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;readable&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; chunk&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chunk &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stdin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		content &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; chunk&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
	When finished, process `content`.
 */&lt;/span&gt;
stdin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;analyze&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; items &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;n? REP .*&#92;n&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/*
		Unique keys, sorted alphabetically
	 */&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; unique &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unique&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&#39;s the final &lt;code&gt;gather&lt;/code&gt; script, piped into &lt;code&gt;sort-gathered.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;gather&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node node_modules/.bin/jscodeshift --no-color --silent --transform=gather-translatables.js js/ | node sort-gathered.js&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; Nathan pointed out on social media that instead of piping into the second &lt;code&gt;sort-gathered.js&lt;/code&gt; script, we could use &lt;code&gt;jscodeshift&lt;/code&gt;&#39;s &lt;a href=&quot;https://github.com/facebook/jscodeshift/issues/398&quot;&gt;undocumented JavaScript API&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;changing-js&quot;&gt;
	&lt;a href=&quot;https://danburzo.ro/javascript-codemods/#changing-js&quot;&gt;#&lt;/a&gt; Changing JavaScript files
&lt;/h2&gt;
&lt;p&gt;Finally, let&#39;s use &lt;code&gt;jscodeshift&lt;/code&gt; for the purpose for which it has been designed: to actually change JavaScript files.&lt;/p&gt;
&lt;p&gt;I&#39;ve recently switched &lt;a href=&quot;https://culorijs.org/&quot;&gt;&lt;code&gt;culori&lt;/code&gt;&lt;/a&gt; to use native ES modules in Node.js for its inaugural &lt;code&gt;1.x&lt;/code&gt; release and realized — by carefully reading &lt;a href=&quot;https://nodejs.org/api/packages.html&quot;&gt;the documentation&lt;/a&gt;, haha just kidding, I promptly got a couple of hundred error messages — that all the imports &lt;strong&gt;need to use the full path&lt;/strong&gt;, including the &lt;code&gt;.js&lt;/code&gt; extension.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// From this:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; interpolate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../interpolate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// …to this:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; interpolate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../interpolate.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally at this point I would despair at the prospect of changing hundreds of declarations by hand, but it was effortless with a transform:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;imports-add-ext.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	jscodeshift transform: adds the &#39;.js&#39; extension
	to all import declarations with relative specifiers:

	From &#39;./file&#39; to &#39;./file.js&#39;, and
	from &#39;../file&#39; to &#39;../file.js&#39;.
 */&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fileInfo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jscodeshift&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;j&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ImportDeclaration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; source &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^&#92;.{1,2}&#92;/&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; node_modules/.bin/jscodeshift &lt;span class=&quot;token parameter variable&quot;&gt;--transform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;imports-add-ext.js src/ test/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The beauty of writing your own transforms is they only have to be as comprehensive as the task requires. In fact, after &lt;a href=&quot;https://github.com/Evercoder/culori/commit/151281cc9e3ac37c1d261139e701e62f673432b6&quot;&gt;changing the &lt;code&gt;import&lt;/code&gt; declarations&lt;/a&gt; I realized I needed to &lt;a href=&quot;https://github.com/Evercoder/culori/commit/10327ea0e7de028a19f5f7ba3ce29f725070822c&quot;&gt;address the &lt;code&gt;export&lt;/code&gt; declarations&lt;/a&gt; as well. Eh.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;A few links from the bookmark archive, filtered for &amp;quot;codemods&amp;quot;, that you may find useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://egghead.io/blog/codemods-with-babel-plugins&quot;&gt;Codemods with Babel Plugins&lt;/a&gt; (2021) by Laurie Barth;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://skovy.dev/codemod-workflow/&quot;&gt;My Workflow for Codemods&lt;/a&gt;(2021) by Spencer Miskoviak;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reaktor.com/blog/an-introduction-to-codemods/&quot;&gt;An Introduction to Codemods: Refactoring JavaScript with JavaScript&lt;/a&gt;(2019) by Tijn Kersjes;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.squarespace.com/blog/2018/building-a-system-for-front-end-translations&quot;&gt;Building a System for Front-End Translations&lt;/a&gt;(2018) by Dan Na;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://padraig.io/automate-refactoring-jscodeshift/&quot;&gt;Automate refactoring with &lt;code&gt;jscodeshift&lt;/code&gt;&lt;/a&gt;(2018) by Patrick Owens;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cpojer/js-codemod&quot;&gt;cpojer/js-codemod&lt;/a&gt;, a collection of &lt;code&gt;jscodeshift&lt;/code&gt; scripts to transform code to next generation JS.&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>How I digitize books</title>
		<link href="https://danburzo.ro/digitizing-books/" />
		<updated>2021-07-09T00:00:00Z</updated>
		<id>https://danburzo.ro/digitizing-books/</id>
		<content type="html"
			>&lt;p&gt;One of my current passions is to publish interesting public domain Romanian books on &lt;a href=&quot;https://llll.ro/&quot;&gt;llll.ro&lt;/a&gt;. The website doesn&#39;t just collect texts already available the web: the idea is to work with the primary sources to provide transcriptions that are faithful to the originals.&lt;/p&gt;
&lt;p&gt;In its journey from paper to HTML, the material goes through a few steps; some are automated, others still rely on manual work. To ensure quality the worflow still needs a final pair of eyes, but we want as little grunt work as possible. This article describes the workflow on which I&#39;ve settled (for now) to bring books online.&lt;/p&gt;
&lt;p&gt;In short, the process entails:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;photographing the pages;&lt;/li&gt;
&lt;li&gt;performing Optical Character Recognition (&lt;abbr&gt;OCR&lt;/abbr&gt;) on them;&lt;/li&gt;
&lt;li&gt;cleaning up and formatting the text.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Image acquisition&lt;/h2&gt;
&lt;p&gt;The first step is to capture clear images of each page. While &lt;abbr title=&quot;Optical Character Recognition&quot;&gt;OCR&lt;/abbr&gt; can work on plain camera photographs, a bit of pre-processing can get you more accurate results. Books tend to have hundreds of pages, so every variation in the ergonomics of this step has significant cumulative effects.&lt;/p&gt;
&lt;p&gt;I use the (free) &lt;a href=&quot;https://evernote.com/products/scannable/&quot;&gt;Scannable app&lt;/a&gt; on iOS to capture, crop, and perspective-correct the pages all in one go. When I&#39;m happy with the crops, I use &lt;span class=&quot;ui&quot;&gt;Send → Share → AirDrop&lt;/span&gt; to transfer the set of images to my computer — just make sure the selected format is PNG, not PDF.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src=&quot;https://danburzo.ro/img/scannable-blecher.jpg&quot; alt=&quot;A page from a Max Blecher book processed with Scannable&quot; width=&quot;300&quot; /&gt;
	&lt;figcaption&gt;A sample image produced by Scannable (&lt;a href=&quot;https://danburzo.ro/img/scannable-blecher.jpg&quot;&gt;see larger size&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Optical character recognition&lt;/h2&gt;
&lt;p&gt;Next, we put the images to good use. For OCR to be truly useful, it has to be &lt;em&gt;very&lt;/em&gt; accurate; otherwise, it&#39;s quicker and less frustrating to transcribe everything by hand than to fix an Unicode character soup.&lt;/p&gt;
&lt;p&gt;For the kinds of texts I&#39;m digitizing — Romanian, possibly using obsolete othography and diacritical marks — the offline OCR tools I&#39;ve tested do a lukewarm job. Online services, for the most part, aren&#39;t much better, with one exception: &lt;a href=&quot;https://cloud.google.com/vision/&quot;&gt;Google&#39;s Vision API&lt;/a&gt; provides near-flawless results. It&#39;s a paid, but cheap, service: at the time of writing, the first 1000 images are free, after which you pay around $1.5 per 1000 images.&lt;/p&gt;
&lt;p&gt;I wrote a little web tool called &lt;a href=&quot;https://llll.ro/tools/vizor&quot;&gt;Vizor&lt;/a&gt; to interact with the Vision API. After you prepare an API key (remember to enable billing for it to work!) you simply drag-and-drop a set of images onto it and get plain text in return.&lt;/p&gt;
&lt;p&gt;The API&#39;s JSON response the text is segmented in a hierarchy of &lt;code&gt;Pages&lt;/code&gt;, &lt;code&gt;Blocks&lt;/code&gt;, &lt;code&gt;Paragraphs&lt;/code&gt;, &lt;code&gt;Words&lt;/code&gt; and &lt;code&gt;Symbols&lt;/code&gt;. Vizor stitches them together according to your needs: you can either preserve the line breaks (&lt;em&gt;Verse&lt;/em&gt;), or obtain nice, flowing paragraphs (&lt;em&gt;Prose&lt;/em&gt;).&lt;/p&gt;
&lt;h2&gt;Cleaning up the text&lt;/h2&gt;
&lt;p&gt;I bring the plain text into Sublime Text and run it through &lt;a href=&quot;https://llll.ro/meta/ocr-checklist&quot;&gt;a series of checks I&#39;ve written down&lt;/a&gt;. These are a series of regular expressions (&lt;em&gt;regexes&lt;/em&gt;) that I plop into Sublime Text&#39;s search field — be sure to enable the regular expression search, and the case (in)sensitivity as needed — to bring out possible problems: the wrong diacritical marks, characters outside of the expected ranges, and so on.&lt;/p&gt;
&lt;p&gt;Then come the manual bits. I mark up the headings, lists, and any bold/italic sections in Markdown syntax. To spell check, I grab the text and paste it into the Pages app, and use its spellchecking tools to identify any typos. The last pass involves eyeballing the entire text to identify any issues I missed with the automated checks. To do this, I produce a PDF or EPUB file from the HTML using &lt;a href=&quot;https://github.com/danburzo/percollate&quot;&gt;percollate&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Produce a PDF file:&lt;/span&gt;
percollate pdf /path/to/doc.html

&lt;span class=&quot;token comment&quot;&gt;# Produce an EPUB file:&lt;/span&gt;
percollate epub /path/to/doc.html&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then load the file on my iPad and skim it for anything out of the ordinary. Any error I spot I highlight or otherwise annotate right inside the app, so they&#39;re easy to review at the end.&lt;/p&gt;
&lt;p&gt;This process allows me to produce high-quality hypertext out of a moderately-sized book in a few evenings&#39; work, with proofreading taking the bulk of the time.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2020</title>
		<link href="https://danburzo.ro/favorite-records-2020/" />
		<updated>2020-12-24T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2020/</id>
		<content type="html"
			>&lt;p&gt;This year has seen a ton of new releases from musicians both familiar and unknown-to-me. I haven&#39;t got around to giving a proper listen to many albums I had on my radar, but here&#39;s a sampler of what I liked, or found interesting, in 2020.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://against-all-logic.bandcamp.com/album/2017-2019&quot;&gt;Against All Logic — 2017-2019&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boomkat.com/products/a-forest-c29998d5-512c-465d-ab6a-20cad1eed549&quot;&gt;Alva Noto — A Forest&lt;/a&gt;, and &lt;a href=&quot;https://boomkat.com/products/unieqav-remixes&quot;&gt;Unieqav Remixes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://anoushkashankar.com/music/singles-eps/love-letters-ep&quot;&gt;Anoushka Shankar — Love Letters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theguardian.com/music/2020/mar/20/baxter-dury-the-night-chancers-review&quot;&gt;Baxter Dury — The Night Chancers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brendan-perry.bandcamp.com/album/songs-of-disenchantment-music-from-the-greek-underground&quot;&gt;Brendan Perry — Songs of Disenchantment: Music from the Greek Underground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bleep.com/release/213397-burial-four-tet-thom-yorke-her-revolution-his-rope&quot;&gt;Burial, Four Tet &amp;amp; Thom Yorke — Her Revolution / His Rope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caribouband.bandcamp.com/album/suddenly&quot;&gt;Caribou — Suddenly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unseenworlds.bandcamp.com/album/ganci-figli&quot;&gt;Carl Stone — Ganci &amp;amp; Figli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/parallels-shellac-reworks-beethoven-christian-loeffler-12169&quot;&gt;Christian Löffler — Parallels: Shellac Reworks (Beethoven)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.azulmusic.com.br/produtos/ver/259/reminiscences-vol.-1.html&quot;&gt;Clara Sverner — Reminiscences (Vol I &amp;amp; II)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danielavery.bandcamp.com/album/love-light&quot;&gt;Daniel Avery — Love + Light&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://room40.bandcamp.com/album/apparition-paintings&quot;&gt;David Toop — Apparition Paintings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devendrabanhart.bandcamp.com/album/vast-ovoid&quot;&gt;Devendra Banhart — Vast Ovoid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dominikeulberg.bandcamp.com/album/mannigfaltig-remixes-pt-2&quot;&gt;Dominik Eulberg — Mannigfaltig Remixes (Pt. 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thou.bandcamp.com/album/may-our-chambers-be-full&quot;&gt;Emma Ruth Rundle &amp;amp; Thou — May our Chambers be Full&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fabriziopaterlini.bandcamp.com/album/transitions-ii&quot;&gt;Fabrizio Paterlini — Transitions II&lt;/a&gt;, &lt;a href=&quot;https://fabriziopaterlini.bandcamp.com/album/transitions-iii&quot;&gt;Transitions III&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fauxreal.bandcamp.com/album/faux-real&quot;&gt;Faux Real — Faux Real&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fourtet.bandcamp.com/album/sixteen-oceans&quot;&gt;Four Tet — Sixteen Oceans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theguardian.com/music/2020/feb/07/makaya-mccraven-gil-scott-heron-were-new-here-review-xl&quot;&gt;Gill Scott-Heron — We&#39;re New Again: a Reimagining by Makaya McCraven&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gyda.bandcamp.com/album/epicycle-ii&quot;&gt;Gyda Valtysdottir — Epicycle II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://haniarani.bandcamp.com/album/home&quot;&gt;Hania Rani — Home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://italtek.bandcamp.com/album/outland&quot;&gt;Ital Tek — Outland&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jesu.bandcamp.com/album/terminus&quot;&gt;Jesu — Terminus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jonsi.bandcamp.com/album/shiver&quot;&gt;Jónsi — Exhale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boomkat.com/products/the-falling-acts&quot;&gt;Kastil — The Falling Acts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://keleketla.bandcamp.com/album/keleketla&quot;&gt;Keleketla! — Keleketla!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kellyleeowens.bandcamp.com/album/inner-song&quot;&gt;Kelly Lee Owens — Inner Song&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kevinmorby.bandcamp.com/album/sundowner&quot;&gt;Kevin Morby — Sundowner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://khruangbin.bandcamp.com/album/mordechai&quot;&gt;Khruangbin — Mordechai&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://laurelhalo.bandcamp.com/album/possessed&quot;&gt;Laurel Halo — Possessed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lidopimienta.bandcamp.com/album/miss-colombia&quot;&gt;Lido Pimienta — Miss Colombia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/gb/album/go-recki-symphony-no-3-symphony-of-sorrowful-songs/1492342486&quot;&gt;Lisa Gerrard &amp;amp; Genesis Orchestra, Conducted by Yordan Kamdzhalov — Górecki Symphony No. 3: Symphony of Sorrowful Songs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://theplaintive.bandcamp.com/album/the-plaintive&quot;&gt;Locust – The Plaintive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mattberninger.bandcamp.com/album/serpentine-prison&quot;&gt;Matt Berninger — Serpentine Prison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://meitei.bandcamp.com/album/kofu&quot;&gt;Meitei — Kofu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thequietus.com/articles/28104-melt-yourself-down-100-yes-review&quot;&gt;Melt Yourself Down — 100% Yes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://michellegurevich.bandcamp.com/album/ecstasy-in-the-shadow-of-ecstasy&quot;&gt;Michelle Gurevich — Ecstasy in the Shadow of Ecstasy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://whitelabrecs.bandcamp.com/album/give-shape-to-space&quot;&gt;Mikael Lind — Give Shape to Space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mogwai.bandcamp.com/album/zerozerozero&quot;&gt;Mogwai — ZEROZEROZERO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://muzztheband.bandcamp.com/album/muzz&quot;&gt;Muzz — Muzz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.deutschegrammophon.com/en/catalogue/products/l-i-t-a-n-i-e-s-nicholas-lens-nick-cave-12128&quot;&gt;Nick Cave &amp;amp; Nicholas Lens — L.I.T.A.N.I.E.S.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nilsfrahm.bandcamp.com/album/empty&quot;&gt;Nils Frahm — Empty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://okaykaya.bandcamp.com/album/surviving-is-the-new-living&quot;&gt;Okay Kaya — Surviving is the New Living&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://somekindofpeace.com/&quot;&gt;Ólafur Arnalds — Some Kind of Peace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://perfumegenius.bandcamp.com/album/set-my-heart-on-fire-immediately&quot;&gt;Perfume Genius — Set My Heart on Fire Immediately&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://music.apple.com/us/album/philip-glass-king-lear-feat-ruth-wilson/1498999090&quot;&gt;Philip Glass ft. Ruth Wilson — King Lear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://phoebebridgers.bandcamp.com/album/punisher&quot;&gt;Phoebe Bridgers — Punisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://robinguthrie.bandcamp.com/album/another-flower&quot;&gt;Robin Guthrie &amp;amp; Harold Budd — Another Flower&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://store.deutschegrammophon.com/p51-i0028948377725/roger-eno-brian-eno/mixing-colours/index.html&quot;&gt;Roger Eno &amp;amp; Brian Eno — Mixing Colours&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://romanfluegel.bandcamp.com/album/acid-test&quot;&gt;Roman Flügel — Acid Test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://rone-music.bandcamp.com/album/room-with-a-view&quot;&gt;Rone — Room with a View&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sarahmarychadwick.bandcamp.com/album/please-daddy-2&quot;&gt;Sarah Mary Chadwick — Please Daddy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sevdaliza.bandcamp.com/album/shabrang&quot;&gt;Sevdaliza — Shabrang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://store.sigurros.com/album/odins-raven-magic-2&quot;&gt;Sigur Rós, Hilmar Örn Hilmarsson, Steindór Andersen, Páll Guðmundsson &amp;amp; Maria Huld Markan Sigfúsdóttir — Odin&#39;s Raven Magic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;★ &lt;a href=&quot;https://slowmeadow.bandcamp.com/album/by-the-ash-tree&quot;&gt;Slow Meadow — By the Ash Tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Folklore_%28Taylor_Swift_album%29&quot;&gt;Taylor Swift — Folklore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tennispagan.bandcamp.com/album/eko&quot;&gt;Tennis Pagan — EKO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thaoandthegetdownstaydown.bandcamp.com/album/temple&quot;&gt;Thao and the Get Down Stay Down — Temple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://duststoredigital.com/album/final-collected-vexations&quot;&gt;The Black Dog — Final Collected Vexations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://modernrecordings.de/shelter/&quot;&gt;Thomas Bartlett — Shelter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tricky.bandcamp.com/album/fall-to-pieces&quot;&gt;Tricky — Fall to Pieces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hominidsounds.bandcamp.com/album/energy-is-forever&quot;&gt;UKAEA — Energy is Forever&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://7klassik.bandcamp.com/album/ambient-layers&quot;&gt;Various Artists — Ambient Layers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boomkat.com/products/uzelli-elektro-saz&quot;&gt;Various Artists — Uzelli Elektro Saz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://whomadewho.bandcamp.com/album/synchronicity&quot;&gt;WhoMadeWho — Synchronicity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://williambasinski.bandcamp.com/album/lamentations&quot;&gt;William Basinski — Lamentations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zebrakatz.bandcamp.com/album/less-is-moor&quot;&gt;Zebra Katz — Less is Moor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a few individual tracks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bbisland.bandcamp.com/track/the-rich-stuff&quot;&gt;Ariel Sharratt &amp;amp; Mathias Kom — The Rich Stuff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danielavery.bandcamp.com/album/dusting-for-smoke&quot;&gt;Daniel Avery — Lone Swordsman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LBc8zZKTqaE&quot;&gt;Dope Lemon — Streets of your Town&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vimeo.com/472686439&quot;&gt;Doves — Carousels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bvsZBdo5pEk&quot;&gt;Jon Hopkins — Dawn Chorus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thomas Feiner — &lt;a href=&quot;https://thomasfeiner.bandcamp.com/track/guide-for-the-perplexed&quot;&gt;Guide for the Perplexed&lt;/a&gt; and &lt;a href=&quot;https://thomasfeiner.bandcamp.com/track/encounters-at-the-end-of-the-world&quot;&gt;Encounters at the End of the World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tindersticks.bandcamp.com/track/youll-have-to-scream-louder&quot;&gt;Tindersticks — You&#39;ll Have to Scream Louder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yasminehamdan.bandcamp.com/track/choubi&quot;&gt;Yasmine Hamdan — Choubi&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://francisbebey.bandcamp.com/track/sanza-tristesse&quot;&gt;Francis Bebey — Sanza Tristesse&lt;/a&gt; (1982–1984)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lists from around the www:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://thequietus.com/articles/29302-the-quietus-top-100-albums-of-2020-norman-records&quot;&gt;The Quietus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.roughtrade.com/gb/albums-of-the-year/&quot;&gt;Rough Trade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bleep.com/top-10-albums-of-the-year-2020&quot;&gt;Bleep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boomkat.com/charts/boomkat-end-of-year-charts-2020&quot;&gt;Boomkat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;R.I.P. Harold Budd (1936–2020)&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>A little npm head-scratcher</title>
		<link href="https://danburzo.ro/a-little-npm-headscratcher/" />
		<updated>2020-11-20T00:00:00Z</updated>
		<id>https://danburzo.ro/a-little-npm-headscratcher/</id>
		<content type="html"
			>&lt;p&gt;The other day I ran into an interesting puzzle, which was fun to sort out, and I learned enough in the process that I thought I should write it down.&lt;/p&gt;
&lt;h2&gt;The setup&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://github.com/danburzo/percollate&quot;&gt;JavaScript project I maintain&lt;/a&gt; has the following file structure, abridged:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
  util/
     slurp.js
  cli-opts.js
test/
  cli-opts.test.js
index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two tools I normally use for JavaScript projects are &lt;code&gt;tape&lt;/code&gt; for tests and &lt;code&gt;eslint&lt;/code&gt; for linting, and these I ultimately ran as:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;tape test/**/*.test.js
eslint &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;src,test&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/**/*.js *.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...but they&#39;re actually stored as npm scripts in my &lt;code&gt;package.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tape test/**/*.test.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;lint&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;eslint {src,test}/**/*.js *.js&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...and then invoked with &lt;code&gt;npm run test&lt;/code&gt; and &lt;code&gt;npm run lint&lt;/code&gt; respectively.&lt;/p&gt;
&lt;p&gt;These commands worked out great. So great that I added them to a pre-commit hook, and ran them in GitHub Actions, to make sure the code is top-shelf quality.&lt;/p&gt;
&lt;p&gt;Then one code change, which passed the pre-commit hooks, suddenly blew up the GitHub action: &lt;code&gt;npm run lint&lt;/code&gt; had found two linting errors in the &lt;code&gt;src/cli-opts.js&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Huh. I fire up my terminal, on which I&#39;ve been running the zsh shell for the last few years, and execute &lt;code&gt;npm run lint&lt;/code&gt;, as one does.&lt;/p&gt;
&lt;p&gt;No errors. What&#39;s going on?&lt;/p&gt;
&lt;p&gt;In the off-chance you want to figure it out yourself (which I imagine is no fun without the actual setup with which to play around), stop here before reading the explanation below.&lt;/p&gt;
&lt;h2&gt;The explanation&lt;/h2&gt;
&lt;p&gt;What&#39;s going on is shells.&lt;/p&gt;
&lt;p&gt;Remember I casually mentioned I run zsh? It turns out this matters. Bringing up the &lt;a href=&quot;https://docs.npmjs.com/cli/v6/commands/npm-run-script&quot;&gt;manual for &lt;code&gt;npm run&lt;/code&gt;&lt;/a&gt; revealed this piece of hitherto unknown information:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The actual shell your script is run within is platform dependent. By default, on Unix-like systems it is the &lt;code&gt;/bin/sh&lt;/code&gt; command, on Windows it is the &lt;code&gt;cmd.exe&lt;/code&gt;. The actual shell referred to by &lt;code&gt;/bin/sh&lt;/code&gt; also depends on the system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Wait a second, you mean to tell me I&#39;ve been running npm scripts with &lt;code&gt;GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)&lt;/code&gt; all along?!&lt;/p&gt;
&lt;p&gt;One aspect for which the choice of shell is important is that different shells have different &lt;a href=&quot;https://en.wikipedia.org/wiki/Glob_%28programming%29&quot;&gt;glob&lt;/a&gt; expansion rules.&lt;/p&gt;
&lt;p&gt;zsh supports &lt;a href=&quot;http://zsh.sourceforge.net/Doc/Release/Expansion.html#Recursive-Globbing&quot;&gt;recursive expansion&lt;/a&gt; of the &lt;code&gt;**/&lt;/code&gt; pattern, so that &lt;code&gt;src/**/*.js&lt;/code&gt; matches both &lt;code&gt;src/util/slurp.js&lt;/code&gt; and &lt;code&gt;src/cli-opts.js&lt;/code&gt; while bash 3.2 only matches the former. It&#39;s only in version 4 that &lt;a href=&quot;https://www.linuxjournal.com/content/globstar-new-bash-globbing-option&quot;&gt;bash gets a &lt;code&gt;globstar&lt;/code&gt; option&lt;/a&gt; that allows recursive expansion, and even that might not be enabled by default.&lt;/p&gt;
&lt;p&gt;Furthermore, when a glob pattern has no matching files, zsh throws an error. By default, bash outputs the glob pattern unchanged.&lt;/p&gt;
&lt;p&gt;To see what the problem might be with these two pairs of diverging behaviors, here are the file structure and the glob patterns again, along with their expansion in zsh and bash:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;src/
  util/
     slurp.js
  cli-opts.js
test/
  cli-opts.test.js
index.js

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; test/**/*.test.js
&lt;span class=&quot;token comment&quot;&gt;# zsh:&lt;/span&gt;
test/cli-opts.test.js
&lt;span class=&quot;token comment&quot;&gt;# bash 3.2:&lt;/span&gt;
test/**/*.test.js

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;src,test&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/**/*.test.js *.js
&lt;span class=&quot;token comment&quot;&gt;# zsh:&lt;/span&gt;
src/util/slurp.js
src/cli-opts.js
index.js
test/cli-opts.test.js

&lt;span class=&quot;token comment&quot;&gt;# bash 3.2:&lt;/span&gt;
src/util/slurp.js
index.js
test/**/*.test.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if we look at &lt;code&gt;tape&lt;/code&gt; and &lt;code&gt;eslint&lt;/code&gt;&#39;s respective &lt;code&gt;package.json&lt;/code&gt; files, we&#39;ll see both use the &lt;a href=&quot;https://github.com/isaacs/node-glob&quot;&gt;glob&lt;/a&gt; package, which lets them accept glob patterns as operands, and to support the &lt;code&gt;**&lt;/code&gt; globstar pattern in their expansion.&lt;/p&gt;
&lt;p&gt;Bash&#39;s behavior on globs it was unable to match, that is to forward them unchanged as operands to the &lt;code&gt;tape&lt;/code&gt; and &lt;code&gt;eslint&lt;/code&gt; commands, is (counterintuitively!) the key thing that held the whole charade together, making bash work almost like zsh.&lt;/p&gt;
&lt;p&gt;But not entirely. If you examine the expansions above closely, you may notice that in bash, we&#39;re not linting one set of files: anything directly under the &lt;code&gt;src/&lt;/code&gt; folder. The linting command had quietly broke the moment I introduced the &lt;code&gt;src/util&lt;/code&gt; subfolder, for reasons I&#39;ll leave as an exercise to the reader :-).&lt;/p&gt;
&lt;p&gt;This subtle detail is why, for the most part, I hadn&#39;t realized anything was wrong with my setup; all tests were run and most files were linted. It was only when a recent mistake in &lt;code&gt;src/cli-opts.js&lt;/code&gt; was caught by GitHub Actions, but not locally, that the jig was up.&lt;/p&gt;
&lt;p&gt;Why &lt;em&gt;did&lt;/em&gt; the &lt;code&gt;lint&lt;/code&gt; command work in GitHub Actions? I have not had the energy to look into it, but my guess is the particular Ubuntu image I&#39;m using may have bash version 4 or newer as its &lt;code&gt;/bin/sh&lt;/code&gt;, with the &lt;code&gt;globstar&lt;/code&gt; option enabled.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;The solution, as always, is to quote glob patterns to prevent their expansion in the shell, and have them delivered intact to the &lt;code&gt;tape&lt;/code&gt; and &lt;code&gt;eslint&lt;/code&gt; commands, which will in turn expand them consistently regardless of the particular shell they&#39;re running in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tape &#39;test/**/*.test.js&#39;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;&quot;lint&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;eslint &#39;{src,test}/**/*.js&#39; &#39;*.js&#39;&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope you found this write-up illuminating!&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Make PNG font samples with ImageMagick</title>
		<link href="https://danburzo.ro/imagemagick-font-samples/" />
		<updated>2020-04-27T00:00:00Z</updated>
		<id>https://danburzo.ro/imagemagick-font-samples/</id>
		<content type="html"
			>&lt;p&gt;In this article we&#39;ll be poking around &lt;a href=&quot;https://imagemagick.org/&quot;&gt;ImageMagick&lt;/a&gt; from the command-line to get it to make great looking PNG previews of sample text typeset in various fonts, like the one below.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/font-preview.png&quot; height=&quot;101&quot; width=&quot;153&quot; alt=&quot;The final result&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;As always with ImageMagick and other tinker-friendly tools, we&#39;ll accidentally learn a few other things along the way.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I&#39;m using the latest version of ImageMagick which, at the time of writing, is &lt;code&gt;7.0.10&lt;/code&gt;. ImageMagick 7 introduces a single &lt;code&gt;magick&lt;/code&gt; command for everything; replace it with &lt;code&gt;convert&lt;/code&gt; in the examples below in case you&#39;re using version 6 or earlier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Writing one line of text&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The quick brown fox jumps over the lazy dog&lt;/em&gt; and &lt;em&gt;The five boxing wizards jump quickly&lt;/em&gt; are two well-known &lt;a href=&quot;https://en.wikipedia.org/wiki/Pangram&quot;&gt;pangrams&lt;/a&gt; — compact phrases that use most of the letters from the Latin alphabet, an aspect which makes them excellent choices for previewing typefaces.&lt;/p&gt;
&lt;p&gt;As our first quest, let&#39;s write the sentence &lt;em&gt;The five boxing wizards jump quickly&lt;/em&gt; on a piece of PNG.&lt;/p&gt;
&lt;p&gt;In ImageMagick, &lt;a href=&quot;https://imagemagick.org/script/command-line-options.php#draw&quot;&gt;the &lt;code&gt;-draw&lt;/code&gt; option&lt;/a&gt; allows us to paint all sorts of things onto an image — pixels, shapes, and text:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 1000x200 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text 50,50 &#39;The five boxing wizards jump quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
sample.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the command above we set up a transparent canvas (&lt;code&gt;canvas:none&lt;/code&gt;) of &lt;code&gt;1000×200&lt;/code&gt; pixels (the &lt;code&gt;-size&lt;/code&gt; option); using a font size of 48 points we then write our phrase starting at the &lt;code&gt;(50,50)&lt;/code&gt; coordinate. The result, shown below with a red border to discern the canvas bounds, is already promising:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/sample.png&quot; height=&quot;101&quot; width=&quot;501&quot; /&gt;
  &lt;figcaption&gt;Our first line of text.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To place the text in a more predictable place on the canvas, for example to keep it vertically aligned regardless of the font in use, handwritten coordinates won&#39;t cut it, especially if we aspire to run the command automatically for lots and lots of fonts. This is where &lt;a href=&quot;https://imagemagick.org/script/command-line-options.php#gravity&quot;&gt;the &lt;code&gt;-gravity&lt;/code&gt; option&lt;/a&gt; comes in handy: with it, we can align the canvas and the text by their matching side. For example, &lt;code&gt;-gravity West&lt;/code&gt; will place the text&#39;s left-hand side against the canvas&#39;s left-hand side:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 1000x200 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-gravity&lt;/span&gt; West &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text 0,0 &#39;The five boxing wizards jump quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
sample-centered.png&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/sample-centered.png&quot; height=&quot;101&quot; width=&quot;501&quot; /&gt;
  &lt;figcaption&gt;The same text, now centered vertically.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Much better! Notice we&#39;ve replaced the &lt;code&gt;(50,50)&lt;/code&gt; coordinate pair with &lt;code&gt;(0,0)&lt;/code&gt;; we don&#39;t want the text to be offset at all from the edge of the image.&lt;/p&gt;
&lt;p&gt;Alternatively, instead of trying to position the text on the canvas, we can make the canvas fit snuggly around the text with the &lt;code&gt;-trim&lt;/code&gt; option:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 1000x200 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-gravity&lt;/span&gt; Center &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text 0,0 &#39;The five boxing wizards jump quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-trim&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
sample-trimmed.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For best results, place the text smack in the middle of the canvas (&lt;code&gt;-gravity Center&lt;/code&gt;), and make sure the canvas is large enough to fit the entire text with a variety of fonts. The canvas gets trimmed in the end, it doesn&#39;t hurt to start with considerable padding.&lt;/p&gt;
&lt;h2&gt;Using specific fonts&lt;/h2&gt;
&lt;p&gt;Up until now we&#39;ve been drawing text with some Helvetica-or-similar font. But the whole point of the exercise was to make PNG previews for &lt;em&gt;fonts&lt;/em&gt;, plural. The &lt;a href=&quot;https://imagemagick.org/script/command-line-options.php#font&quot;&gt;&lt;code&gt;-font&lt;/code&gt; option&lt;/a&gt; takes a path to a font file (&lt;code&gt;.ttf&lt;/code&gt;, &lt;code&gt;.otf&lt;/code&gt;, etc.) to use for drawing all the text:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 1000x200 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-font&lt;/span&gt; ./MyFont.otf
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-gravity&lt;/span&gt; Center &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text 0,0 &#39;The five boxing wizards jump quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-trim&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
MyFont-sample.png&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;-font&lt;/code&gt; option also (technically, sort of) allows you to specify the font by its name instead of pointing to a font file; but the available fonts are not always, as one would assume, the fonts installed on your your machine. Instead you&#39;re bound to the list you see when you run &lt;code&gt;magick -list font&lt;/code&gt;; what it contains depends on your operating system. On macOS, you might find &lt;a href=&quot;https://github.com/testdouble/imagemagick-macos-font-setup&quot;&gt;this script&lt;/a&gt; by Justin Searls useful for making ImageMagick aware of system fonts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What&#39;s better than using a custom font? Using a whole bunch of custom fonts! In a folder full of &lt;code&gt;.otf&lt;/code&gt; font files, we can generate PNG samples for them in bulk using a for-loop:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;font&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; *.otf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 1000x200 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-font&lt;/span&gt; ./&lt;span class=&quot;token variable&quot;&gt;$font&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-gravity&lt;/span&gt; Center &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text 0,0 &#39;The five boxing wizards jump quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-trim&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$font&lt;/span&gt;-sample.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;$font&lt;/code&gt; variable holding the path to the current font shows up twice in the command: as &lt;code&gt;-font ./$font&lt;/code&gt; for writing text, and as part of the output filename, &lt;code&gt;$font-sample.png&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This gets us nicely named, nicely trimmed, previews:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/collated-samples.png&quot; height=&quot;322&quot; width=&quot;422&quot; /&gt;
  &lt;figcaption&gt;Samples in various fonts.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Making a GIF out of the images&lt;/h2&gt;
&lt;p&gt;I thought &lt;code&gt;ffmpeg&lt;/code&gt; is a good tool to make an animated GIF that uses the PNGs as frames, but &lt;a href=&quot;https://stackoverflow.com/questions/14676119/imagemagick-and-transparent-background-for-animated-gif#14676207&quot;&gt;this Stack Overflow answer&lt;/a&gt; showed me a quicker solution with ImageMagick&#39;s &lt;code&gt;convert&lt;/code&gt; utility:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick convert &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-delay&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-dispose&lt;/span&gt; Previous &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
*.png fonts.gif&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-delay&lt;/code&gt; option sets how much each frame in the animation lasts, with &lt;code&gt;0&lt;/code&gt; having the frames play as fast as possible. The &lt;code&gt;-dispose Previous&lt;/code&gt; option clears the canvas after each frame, instead of overlaying frames on top of each other.&lt;/p&gt;
&lt;figure&gt;
  &lt;img style=&quot;border: 1px solid&quot; src=&quot;https://danburzo.ro/img/imagemagick-font-previews/fonts.gif&quot; height=&quot;100&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;All the samples as one animated GIF.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A beautiful animated GIF where... the canvas is &lt;code&gt;1000×200&lt;/code&gt; pixels in size and all the texts are centered?! As far as we know, every individual sample is trimmed to a different size!&lt;/p&gt;
&lt;p&gt;Turns out that ImageMagick tags images it produces with metadata it can, and will, then use if the image file is further processed. &lt;a href=&quot;https://imagemagick.org/script/identify.php&quot;&gt;The &lt;code&gt;identify&lt;/code&gt; command&lt;/a&gt; lists out the metadata:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick identify &lt;span class=&quot;token parameter variable&quot;&gt;-verbose&lt;/span&gt; MyFont-sample.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s an abridged version of the output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Image: MyFont-sample.png
  Format: PNG (Portable Network Graphics)
  (...)
  Geometry: 738x45+0+0
  (...)
  Page geometry: 1000x200+130+76
  Origin geometry: +130+76
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It now becomes clear that the &lt;code&gt;-trim&lt;/code&gt; option we had used to generate the PNGs stores the position of the trimmed area on the original canvas as the &lt;em&gt;Page geometry&lt;/em&gt; metadata. Flashback to &lt;a href=&quot;https://imagemagick.org/script/command-line-options.php#trim&quot;&gt;all the words in the docs&lt;/a&gt; and the bit about &lt;code&gt;+repage&lt;/code&gt; starts to make sense:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The page or virtual canvas information of the image is preserved allowing you to extract the result of the &lt;code&gt;-trim&lt;/code&gt; operation from the image. Use a &lt;code&gt;+repage&lt;/code&gt; to remove the virtual canvas page information if it is unwanted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To remove the extraneous &lt;em&gt;Page geometry&lt;/em&gt; metadata, the adjusted command is:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 1000x200 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-font&lt;/span&gt; ./MyFont.otf &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-gravity&lt;/span&gt; Center &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text 0,0 &#39;The five boxing wizards jump quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-trim&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  +repage &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
MyFont-sample.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure enough, the same(-ish) command to build the GIF:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick convert &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-delay&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-dispose&lt;/span&gt; Previous &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-layers&lt;/span&gt; trim-bounds &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
*.png fonts.gif&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now does what we&#39;d expected it to do in the first place:&lt;/p&gt;
&lt;figure&gt;
  &lt;img style=&quot;border: 1px solid&quot; src=&quot;https://danburzo.ro/img/imagemagick-font-previews/fonts-adjusted.gif&quot; height=&quot;37&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;All the samples as one animated GIF, properly sized and aligned.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Not that it&#39;s in any way better, but, you know, for the sake of completeness. And yes, I cheated a smidge. I added in the &lt;code&gt;-layers trim-bounds&lt;/code&gt; option, &lt;em&gt;on the house&lt;/em&gt;, to spare you the disappointment of finding out what ImageMagick does by default when it encounters frames of different sizes. Let me just say the option enlarges the canvas so that all the frames fit entirely.&lt;/p&gt;
&lt;p&gt;After this brief foray into GIF-making, let&#39;s get back to the subject at hand.&lt;/p&gt;
&lt;h2&gt;Writing more than one line of text&lt;/h2&gt;
&lt;p&gt;ImageMagick doesn&#39;t have any built-in mechanisms to lay out text on multiple lines, so we have to do it manually, line by line. The &lt;code&gt;-draw&lt;/code&gt; routine becomes:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &#92;
  text 50,64 &#39;The five boxing&#39; &#92;
  text 50,128 &#39;wizards jump&#39; &#92;
  text 50,192 &#39;quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we devise a line height of 64 points, and place the lines of text at &lt;code&gt;64×1&lt;/code&gt;, &lt;code&gt;64×2&lt;/code&gt;, and so on. On the horizontal axis, we&#39;re not starting the text directly at the edge of the image, because the flourishes on &lt;a href=&quot;https://fonts.google.com/specimen/Monsieur+La+Doulaise&quot;&gt;some of the more decorated typefaces&lt;/a&gt; may get cut off. With a 50-point offset, we give the font ample space to do its thing, and we&#39;ll get rid of the excess later, using the &lt;code&gt;-trim&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;The full command is presented below:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 640x320 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-font&lt;/span&gt; ./MyFont.otf &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &#92;
    text 50,64 &#39;The five boxing&#39; &#92;
    text 50,128 &#39;wizards jump&#39; &#92;
    text 50,192 &#39;quickly.&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-trim&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  +repage &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
MyFont-sample.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And produces images such as this one:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/myfont-sample.png&quot; height=&quot;173&quot; width=&quot;273&quot; /&gt;
  &lt;figcaption&gt;A multiline font sample.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;But when put side by side, we notice the baselines don&#39;t align across images.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/side-by-side.png&quot; height=&quot;204&quot; width=&quot;1491&quot; /&gt;
  &lt;figcaption&gt;Font samples side by side.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If we put the trimmed images side by side, we can tell the baselines don&#39;t match. Ideally, we only want to trim the excess space on three of the edges, and leave the top edge as-is, so that lines begin at the 64-point mark regardless of the font.&lt;/p&gt;
&lt;p&gt;There must be more than one way to achieve this, but one that I found straightforward is to add a pixel guard at the top, trim normally, then chop the pixel off:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;magick &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-size&lt;/span&gt; 640x320 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  canvas:none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-font&lt;/span&gt; ./MyFont.otf &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-pointsize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-background&lt;/span&gt; none &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-splice&lt;/span&gt; 0x1 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-draw&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &#92;
    text 50,64 &#39;The five boxing&#39; &#92;
    text 50,128 &#39;wizards jump&#39; &#92;
    text 50,192 &#39;quickly.&#39; &#92;
    point 50,0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-trim&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-chop&lt;/span&gt; 0x1 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  +repage &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
MyFont-sample.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firstly, &lt;code&gt;-splice 0x1&lt;/code&gt; adds a &lt;em&gt;transparent&lt;/em&gt; (by virtue of the previous &lt;code&gt;-background none&lt;/code&gt; declaration) pixel row at the top of the image. Then &lt;code&gt;-draw point 50,0&lt;/code&gt; places a solid black pixel on that top row, which causes the subsequent &lt;code&gt;-trim&lt;/code&gt; option to only crop transparent pixels along the other three edges. Finally, &lt;code&gt;-chop 0x1&lt;/code&gt; removes the row we added earlier. This makes the text in all images line up nicely.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://danburzo.ro/img/imagemagick-font-previews/side-by-side-aligned.png&quot; height=&quot;206&quot; width=&quot;1491&quot; /&gt;
  &lt;figcaption&gt;Font samples side by side, properly aligned.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That&#39;s it! That&#39;s how you make PNG font samples with ImageMagick.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2019</title>
		<link href="https://danburzo.ro/favorite-records-2019/" />
		<updated>2020-01-03T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2019/</id>
		<content type="html"
			>&lt;p&gt;&lt;em&gt;As you can notice, 2019 had a bit of a work-in-progress vibe to it.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Top records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sharon Van Etten — Remind Me Tomorrow&lt;/li&gt;
&lt;li&gt;The National — I Am Easy To Find&lt;/li&gt;
&lt;li&gt;Vanessa Wagner — Inland / Inland Versions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jacquesgreene.bandcamp.com/album/dawn-chorus&quot;&gt;Jacques Greene — Dawn Chorus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other records of note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Abul Mogard&lt;/li&gt;
&lt;li&gt;Acid Arab — Jdid&lt;/li&gt;
&lt;li&gt;Aldous Harding — Designer&lt;/li&gt;
&lt;li&gt;Alex Cameron — Miami Memory&lt;/li&gt;
&lt;li&gt;Altin Gün — Gece&lt;/li&gt;
&lt;li&gt;Angel Olsen — All Mirrors&lt;/li&gt;
&lt;li&gt;Bedouine — Bird Songs of a Killjoy&lt;/li&gt;
&lt;li&gt;Beirut — Gallipoli&lt;/li&gt;
&lt;li&gt;Blanck Mass — Animated Violence Mild&lt;/li&gt;
&lt;li&gt;Blawan — Many Many Pings&lt;/li&gt;
&lt;li&gt;Devendra Banhart — Ma&lt;/li&gt;
&lt;li&gt;Fennesz — Agora&lt;/li&gt;
&lt;li&gt;Floating Points — Crush&lt;/li&gt;
&lt;li&gt;Helado Negro — This Is How You Smile&lt;/li&gt;
&lt;li&gt;Holly Herndon — Proto&lt;/li&gt;
&lt;li&gt;Jay-Jay Johanson — Kings Cross&lt;/li&gt;
&lt;li&gt;Kangding Ray — Azores EP&lt;/li&gt;
&lt;li&gt;Kastil&lt;/li&gt;
&lt;li&gt;Kazu — Adult Baby&lt;/li&gt;
&lt;li&gt;Lana del Rey — NFR!&lt;/li&gt;
&lt;li&gt;Nick Cave &amp;amp; the Bad Seeds — Ghosteen&lt;/li&gt;
&lt;li&gt;Nikveh&lt;/li&gt;
&lt;li&gt;Rhye — Spirit&lt;/li&gt;
&lt;li&gt;Slow Meadow — Happy Occident&lt;/li&gt;
&lt;li&gt;Sun O)))) — Life Metal&lt;/li&gt;
&lt;li&gt;Thom Yorke — Anima&lt;/li&gt;
&lt;li&gt;Tigran Hamasyan — They Say Everything Stays The Same&lt;/li&gt;
&lt;li&gt;Tool&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Prevent history navigation on horizontally-scrolling elements with CSS</title>
		<link href="https://danburzo.ro/css-overscroll-behavior/" />
		<updated>2019-10-16T00:00:00Z</updated>
		<id>https://danburzo.ro/css-overscroll-behavior/</id>
		<content type="html"
			>&lt;p&gt;Behold a horizontally-scrolling element:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.reel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;overflow-x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; scroll&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern is particularly useful on mobile devices as &lt;a href=&quot;https://hankchizljaw.com/wrote/progressive-overflow-management-with-a-scroll-track-utility/&quot;&gt;an alternative to stacking&lt;/a&gt; for breaking a grid apart, but its utility is not limited to small screens. A horizontally-scrolling section can also become the basis of an image slideshow on desktop browsers.&lt;/p&gt;
&lt;p&gt;When people use the trackpad for navigating horizontally (often, with a two-finger gesture), we get an unintended side-effect: the gesture coincides with navigating through the browser history. A two-finger swipe to the right triggers the browser Back action, while two fingers to the left triggers the Forward action. When you reach the edges as you scroll the container, you often accidentally navigate away from the page.&lt;/p&gt;
&lt;p&gt;To prevent the navigation, we can use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior&quot;&gt;&lt;code&gt;overscroll-behavior&lt;/code&gt;&lt;/a&gt; CSS property. A value of &lt;code&gt;none&lt;/code&gt; or &lt;code&gt;contain&lt;/code&gt; will make sure the excess scroll does not ripple to the container&#39;s ancestors and, ultimately, the page:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.reel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;overflow-x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; scroll&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;overscroll-behavior-x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; contain&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s &lt;a href=&quot;https://danburzo.ro/demos/overscroll-behavior.html&quot;&gt;a demo highlighting the difference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At the time of writing, this property is supported in Firefox &lt;sup&gt;&lt;a href=&quot;https://danburzo.ro/css-overscroll-behavior/#fn-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, Chrome &lt;sup&gt;&lt;a href=&quot;https://danburzo.ro/css-overscroll-behavior/#fn-2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, and Edge, so most desktop users will benefit from the enhanced behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For extra credit&lt;/strong&gt; also opt into smooth, accelerated scrolling in mobile Safari with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-overflow-scrolling&quot;&gt;&lt;code&gt;-webkit-overflow-scrolling: touch&lt;/code&gt;&lt;/a&gt;. While iOS / iPadOS 13 &lt;a href=&quot;https://developer.apple.com/documentation/safari_release_notes/safari_13_release_notes&quot;&gt;makes this the default&lt;/a&gt;, it&#39;s still a nice, ahem, &lt;em&gt;touch&lt;/em&gt; for older versions.&lt;/p&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;March 26, 2022:&lt;/strong&gt; The &lt;code&gt;overscroll-behavior&lt;/code&gt; CSS property is now available for testing in &lt;a href=&quot;https://webkit.org/blog/12522/release-notes-for-safari-technology-preview-142/&quot;&gt;Safari Technology Preview 142&lt;/a&gt; (yay!)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;August 7, 2022:&lt;/strong&gt; But history navigation is explicitly allowed despite &lt;code&gt;overscroll-behavior&lt;/code&gt; (sad tromobone) [&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=240183&quot;&gt;WebKit#240183&lt;/a&gt;].&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;sup id=&quot;fn-1&quot;&gt;1&lt;/sup&gt; There&#39;s a &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1595791&quot;&gt;small issue in Firefox&lt;/a&gt; with the first swipe.&lt;/p&gt;
&lt;p&gt;&lt;sup id=&quot;fn-2&quot;&gt;2&lt;/sup&gt; I stumbled upon a peculiar combination &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1010454#c_ts1570106892&quot;&gt;that made Chrome ignore the declaration&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Prevent the need to zoom into inputs with CSS</title>
		<link href="https://danburzo.ro/css-safari-zoom-inputs/" />
		<updated>2019-10-08T00:00:00Z</updated>
		<id>https://danburzo.ro/css-safari-zoom-inputs/</id>
		<content type="html"
			>&lt;p&gt;Adrian Roselli describes &lt;a href=&quot;https://adrianroselli.com/2019/09/under-engineered-text-boxen.html&quot;&gt;a quick, effective way&lt;/a&gt; to make &lt;abbr&gt;HTML&lt;/abbr&gt; inputs inherit the styles of the page they’re on, rather than deriving their appearance from operating system conventions. The following (simplified) declaration makes the font match the input’s surroundings; in particular, it makes its &lt;code&gt;font-size&lt;/code&gt; equivalent to &lt;code&gt;1em&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;textarea,
input&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inherit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use mobile Safari, you’ve probably noticed that it sometimes zooms into inputs as you focus them, to make them easier to read and type into. This happens whenever an input uses text smaller than &lt;code&gt;16px&lt;/code&gt;, and it’s a sensible thing to do. However, in some cases, the effect is purely accidental: you get this &lt;em&gt;barely zooming into an input&lt;/em&gt; because it happens to have a computed font size of &lt;code&gt;15px&lt;/code&gt;, or &lt;code&gt;14px&lt;/code&gt;, as inherited from its parent.&lt;/p&gt;
&lt;p&gt;To proactively fix this, and spare the user a potentially jarring interaction, I got into the habit of throwing in an extra declaration in my &lt;a href=&quot;https://danburzo.ro/snippets/css-reset/&quot;&gt;default stylesheet&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button,
select,
textarea,
input&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;16px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 1em&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In browsers supporting &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/max&quot;&gt;the &lt;code&gt;max()&lt;/code&gt; function&lt;/a&gt; — including, thankfully, Safari — it prevents the inherited font size of inputs from going below &lt;code&gt;16px&lt;/code&gt;, and avoids the zoom behavior.&lt;/p&gt;
&lt;p&gt;Nice and easy!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This technique only applies to 100% zoom; starting from version 13, mobile Safari has zoom controls, and zooming out of the page understandably brings back the &lt;em&gt;zoom into input&lt;/em&gt; thing.&lt;/li&gt;
&lt;li&gt;One thing that’s still prevalent is using &lt;code&gt;maximum-scale=1.0&lt;/code&gt;, or &lt;code&gt;user-scalable=no&lt;/code&gt; in the &lt;code&gt;viewport&lt;/code&gt; meta tag. Despite Safari ignoring them since iOS 10 to allow users to pinch-zoom regardless, they’re &lt;a href=&quot;https://a11yproject.com/posts/never-use-maximum-scale/&quot;&gt;still bad for accesibility&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Making sense of units in CSS Media Queries (in the year 2019)</title>
		<link href="https://danburzo.ro/media-query-units/" />
		<updated>2019-09-29T00:00:00Z</updated>
		<id>https://danburzo.ro/media-query-units/</id>
		<content type="html"
			>&lt;p&gt;As browsers are starting to ship parts of the &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/&quot;&gt;Media Queries Level 5&lt;/a&gt; spec, recent discussion on CSS media queries understandably revolves around using these new features to better adjust web pages to users&#39; needs and preferences. In 2019, the old-school queries &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries&quot;&gt;published in 2012&lt;/a&gt; as a W3C Recommendation, and near-universally supported across browsers, seem to have been exhaustively dissected and discussed.&lt;/p&gt;
&lt;p&gt;Still, I felt some aspects of how they work continued to elude me. In this article I set out to clarify them.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Quick recap: media queries are a mechanism in CSS and HTML (with additional hooks for JavaScript) which lets us test certain aspects of the browser and device that display our web page. These aspects are external to the page, and not (usually) influenced by the styles we apply to it.&lt;/p&gt;
&lt;p&gt;There are &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Media_features&quot;&gt;many features&lt;/a&gt; we can test. I&#39;m going to look at a tiny slice: the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; media features, with their associated &lt;code&gt;min-&lt;/code&gt; and &lt;code&gt;max-&lt;/code&gt; queries. They tell us things about the size of the space allocated for the web page.&lt;/p&gt;
&lt;p&gt;A media query inside CSS uses the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media&quot;&gt;&lt;code&gt;@media&lt;/code&gt; rule&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
	Makes the page red whenever there are 
	at least 400px available for it in the browser.
 */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 400px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we qualify the &lt;code&gt;width&lt;/code&gt; feature with the &lt;code&gt;min-&lt;/code&gt; prefix, the query reads as &lt;em&gt;at least&lt;/em&gt;, while the &lt;code&gt;max-&lt;/code&gt; stands for &lt;em&gt;at most&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Media Queries Level 4 specification introduces &lt;a href=&quot;https://www.w3.org/TR/mediaqueries-4/#mq-range-context&quot;&gt;a clearer syntax&lt;/a&gt;: &lt;code&gt;(width &amp;gt;= 400px)&lt;/code&gt; instead of &lt;code&gt;(min-width: 400px)&lt;/code&gt;. Since it&#39;s a relatively new addition, it&#39;s not a good replacement for the classic syntax yet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Are dimension queries useful?&lt;/h2&gt;
&lt;p&gt;Knowing what constraints the browser imposes on our page is useful for making adjustments to the layout to better adapt it to the myriad of screens on which it can potentially be displayed. Media queries have been &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design#Media_Queries&quot;&gt;pivotal to propelling Responsive Web Design&lt;/a&gt; into ubiquity.&lt;/p&gt;
&lt;p&gt;With new, more powerful, ways of expressing layout such as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout&quot;&gt;Flexible Box&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout&quot;&gt;Grid&lt;/a&gt;, CSS gains alternatives to media queries for responsive layouts. &lt;a href=&quot;https://every-layout.dev/&quot;&gt;Every Layout&lt;/a&gt; by Heydon Pickering and Andy Bell is an excellent resource to get a feel for using &lt;code&gt;flex&lt;/code&gt; and &lt;code&gt;grid&lt;/code&gt; properties, often combined with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/calc&quot;&gt;&lt;code&gt;calc()&lt;/code&gt;&lt;/a&gt;, for common patterns normally solved by media queries. CSS is also getting the &lt;code&gt;min()&lt;/code&gt;, &lt;code&gt;max()&lt;/code&gt;, and &lt;code&gt;clamp()&lt;/code&gt; &lt;a href=&quot;https://drafts.csswg.org/css-values-4/#comp-func&quot;&gt;comparison functions&lt;/a&gt;, which extend &lt;code&gt;min-width&lt;/code&gt;/&lt;code&gt;max-width&lt;/code&gt;/&lt;code&gt;min-height&lt;/code&gt;/&lt;code&gt;max-height&lt;/code&gt; to all properties, and promise to further erode dimension queries&#39; territory.&lt;/p&gt;
&lt;p&gt;Although these recent developments don&#39;t make dimension queries obsolete, they relegate them to an &lt;a href=&quot;https://www.smashingmagazine.com/2018/02/media-queries-responsive-design-2018/&quot;&gt;auxiliary role in responsive design&lt;/a&gt;. That&#39;s a good thing! Media queries are &lt;em&gt;coarse&lt;/em&gt;, and best suited to make top-level adjustments based on top-level constraints.&lt;/p&gt;
&lt;p&gt;Dimension queries are not strictly a CSS thing, eiher. In HTML, width queries also show up in the &lt;code&gt;sizes&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements to enable &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images&quot;&gt;responsive images&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All in all, it seems we can&#39;t Marie Kondo them out of our web design toolbox just yet, so let&#39;s see how we can use dimension queries efficiently. From here on, I&#39;m going to call them just &lt;em&gt;media queries&lt;/em&gt;, since they&#39;re the only ones discussed.&lt;/p&gt;
&lt;h2&gt;Units in media queries&lt;/h2&gt;
&lt;p&gt;How does our choice of CSS units in media queries influence our design, and our users&#39; ability to express preferences for their experience?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Devices have screens made out of pixels. The browser takes part of those device pixels to display a web page in. The narrower the browser, the fewer pixels we get. CSS has the &lt;code&gt;px&lt;/code&gt; unit, short for &lt;em&gt;pixel&lt;/em&gt;. For the sake of simplicity let&#39;s gloss over, for a short while, how CSS pixels are not the same thing as device pixels. They &lt;em&gt;sure feel&lt;/em&gt;, at first brush, like they&#39;re the same.&lt;/p&gt;
&lt;p&gt;Writing media queries in pixels is pretty straightforward, feels intuitive (especially coming from graphic design tools), and produces the result we expect. At least on the screen for which we&#39;ve designed it.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;px&lt;/code&gt; in media queries, and in general, go against the grain of the web. Content should flow like water regardless of the vessel holding it, and pixels make it akin to a lifeless lump of coal.&lt;/p&gt;
&lt;p&gt;CSS gives us font-relative CSS units — &lt;code&gt;em&lt;/code&gt;, &lt;code&gt;rem&lt;/code&gt;, and their friends — which allow us to write styles in harmony to the content we want to display. When used in media queries, like we intuit we should, what do they &lt;em&gt;relate&lt;/em&gt; to exactly?&lt;/p&gt;
&lt;p&gt;According to the spec, they relate to the &lt;em&gt;initial&lt;/em&gt; value of font properties. For &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;rem&lt;/code&gt;, the relevant property is &lt;code&gt;font-size&lt;/code&gt;, which most browsers initially set to &lt;code&gt;16px&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As such, changing the font size of the &lt;code&gt;html&lt;/code&gt; element:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.25rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...detaches the meaning of &lt;code&gt;rem&lt;/code&gt;s in your styles from the meaning of &lt;code&gt;rem&lt;/code&gt;s in media queries: &lt;code&gt;1rem&lt;/code&gt; in styles is now equivalent to &lt;code&gt;20px&lt;/code&gt;, while in media queries &lt;code&gt;1rem&lt;/code&gt;, and &lt;code&gt;1em&lt;/code&gt; for that matter, is still &lt;code&gt;16px&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why would the spec mandate this in the first place?&lt;/p&gt;
&lt;p&gt;The CSS Working Group explain in a FAQ entry that &lt;a href=&quot;https://wiki.csswg.org/FAQ#selectors-that-depend-on-layout&quot;&gt;selectors can&#39;t depend on layout&lt;/a&gt;. If &lt;code&gt;rem&lt;/code&gt; / &lt;code&gt;em&lt;/code&gt; media queries depended on the &lt;code&gt;font-size&lt;/code&gt; of the &lt;code&gt;html&lt;/code&gt; element, you could create an infinite loop:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 60rem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* 
			Setting this invalidates 
			the media query selector 
			that triggered this style.
		*/&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes things a bit more cumbersome, but the limitation makes sense. You take a mental note of this peculiarity, and make sure you always think of media queries in terms of initial sizes. Suddenly —&lt;/p&gt;
&lt;h3&gt;The trouble with Safari&lt;/h3&gt;
&lt;p&gt;Current browsers generally adhere to the spec in regards to font-relative units in media queries. The big outlier is Safari, on both desktop and mobile.&lt;/p&gt;
&lt;p&gt;Safari follows the spec for most relative units: &lt;code&gt;1em&lt;/code&gt; in media queries is &lt;code&gt;16px&lt;/code&gt; regardless of the font size on the &lt;code&gt;html&lt;/code&gt; element. But it scales &lt;code&gt;rem&lt;/code&gt;s in particular in accordance to the &lt;code&gt;html&lt;/code&gt; element (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=156684&quot;&gt;WebKit#156684&lt;/a&gt;, fixed in &lt;a href=&quot;https://webkit.org/blog/12156/release-notes-for-safari-technology-preview-137/&quot;&gt;Safari TP 137&lt;/a&gt;). Since this breaks the &amp;quot;no layout-dependent selectors&amp;quot; CSS rule, we can actually &lt;a href=&quot;https://danburzo.ro/demos/browser-summary/safari.html&quot;&gt;witness the infinite loop&lt;/a&gt; described above.&lt;/p&gt;
&lt;p&gt;Got us there, Safari! But since we can use &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;rem&lt;/code&gt; interchangeably, as they relate to the same thing in any spec-respecting browser, we just pick the not-broken one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If we stick to &lt;code&gt;em&lt;/code&gt; in media queries, we bring Safari&#39;s behavior in line with the other browsers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;User preferences&lt;/h2&gt;
&lt;p&gt;Users have a few ways of adjusting their experience of a web page. Let&#39;s go through them, one by one, to see their impact on our choice of units for media queries.&lt;/p&gt;
&lt;h3&gt;Zooming in&lt;/h3&gt;
&lt;p&gt;Remember the whole &lt;em&gt;pixels are not pixels&lt;/em&gt; thing we avoided earlier? It becomes key to how zoom works. On screens, relative units resolve to &lt;code&gt;px&lt;/code&gt;. These are &lt;em&gt;CSS pixels&lt;/em&gt;, distinct from physical pixels on the device. They map to physical pixels based on the device&#39;s pixel density. You can read more about it in &lt;a href=&quot;https://hacks.mozilla.org/2013/09/css-length-explained/&quot;&gt;CSS Length Explained&lt;/a&gt;, but what matters is there&#39;s a certain &lt;em&gt;ratio&lt;/em&gt; between what constitutes a pixel in CSS and on the device, so that you can experience one CSS pixel roughly the same across devices.&lt;/p&gt;
&lt;p&gt;When you change the zoom level, modern browsers will &lt;strong&gt;tweak the ratio between CSS pixels and device pixels&lt;/strong&gt;. &lt;em&gt;1px&lt;/em&gt; in CSS ends up meaning two device pixels, or four, or half a pixel. This is a brilliant way to keep the layout mostly intact, regardless of the choice of CSS units in stylesheets.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1rem&lt;/code&gt; is still &lt;code&gt;16px&lt;/code&gt; when you zoom in, but now there are fewer (CSS) pixels available to your page. This reflects in the media queries: &lt;code&gt;min-width&lt;/code&gt; has a lower threshold — fewer pixels, fewer &lt;code&gt;rem&lt;/code&gt;s, fewer &lt;code&gt;em&lt;/code&gt;s. Suddenly —&lt;/p&gt;
&lt;h4&gt;The trouble with Safari (again)&lt;/h4&gt;
&lt;p&gt;Safari on macOS has a bug where &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;rem&lt;/code&gt; units in media queries get the browser&#39;s zoom level factored in (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=156687&quot;&gt;WebKit#156687&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;As you zoom in, &lt;code&gt;1rem&lt;/code&gt; becomes &lt;code&gt;20px&lt;/code&gt;, and then &lt;code&gt;28px&lt;/code&gt; in media queries. This is concerning because the zoom level is now doubly-represented: once by the fact that we get fewer CSS pixels for the page, and again by it inflating our &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;rem&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;With iOS 13, and the new iPadOS, Safari also introduced zoom controls for mobile users. They work &lt;em&gt;much&lt;/em&gt; better to adjust the layout than the &lt;em&gt;Request Desktop/Mobile Website&lt;/em&gt; feature, which does nothing on websites built on responsive design principles. They&#39;re part of the reason I wanted to learn more about media queries and zooming.&lt;/p&gt;
&lt;p&gt;Thankfully, the zoom controls on iOS 13 work in accordance to the rest of the browsers. (But a glance at desktop Safari 13, currently in the Technology Preview stage, reveals it still exhibits the bug.)&lt;/p&gt;
&lt;p&gt;Since it&#39;s isolated to desktops (thus, larger screens), the behavior macOS Safari is not the end of the world. As the user zooms in, Safari thinks it has fewer &lt;code&gt;em&lt;/code&gt;s available than it actually has, and triggers a mobile-friendly layout sooner than it needs to — no biggie. When zooming out, what can happen is the layout shrinks disproportionately to the text, and may warrant some extra attention.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fun with &lt;code&gt;vw&lt;/code&gt;.&lt;/strong&gt; Safari on macOS applies the zoom level to all relative units, and that includes &lt;code&gt;vw&lt;/code&gt;. Yes, &lt;code&gt;min-width&lt;/code&gt; and &lt;code&gt;max-width&lt;/code&gt; don&#39;t always match &lt;code&gt;100vw&lt;/code&gt;. Why, that means we can use media queries to &lt;a href=&quot;https://danburzo.ro/demos/browser-summary/safari-zoom.html&quot;&gt;detect the zoom level&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;We can use this quirk to our advantage, and employ media queries to fix any broken aspects of the layout in zoomed-out Safari. Since &lt;code&gt;vw&lt;/code&gt; media queries are not affected by the &lt;html&gt; element&#39;s font size the way &lt;code&gt;rem&lt;/code&gt;s are, we can, if that helps in any way, go ahead and adjust it:&lt;/html&gt;&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 133.33vw&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/* the zoom level in Safari is at most 75% */&lt;/span&gt;
	&lt;span class=&quot;token selector&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Something smaller than usual */&lt;/span&gt;
		&lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.9em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, &lt;code&gt;133.33&lt;/code&gt; comes from dividing 100 with the maximum zoom level we want to match, in our case &lt;code&gt;0.75&lt;/code&gt; (75%).&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Adjusting the zoom level is common, as it&#39;s readily available in menus and via keyboard shortcuts, and browsers do a good job of honoring it.&lt;/p&gt;
&lt;h3&gt;Changing the font settings&lt;/h3&gt;
&lt;p&gt;Users have other points of leverage hidden among the browser preferences — adjusting how text is displayed on web pages. It comes in (at least) two distinct flavors:&lt;/p&gt;
&lt;h4&gt;Changing the default size&lt;/h4&gt;
&lt;p&gt;So far we haven&#39;t really examined an assumption we made earlier: that the initial font size in browsers is &lt;code&gt;16px&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://medium.com/@vamptvo/pixels-vs-ems-users-do-change-font-size-5cfb20831773&quot;&gt;research by Evan Minto&lt;/a&gt;, around 3% of users navigate the web at other sizes, either because browsers themselves have a default size other than &lt;code&gt;16px&lt;/code&gt;, or the user has changed the default.&lt;/p&gt;
&lt;p&gt;The distinction doesn&#39;t matter, as both have the same effect. Whatever the initial font size the browser offers (either its default, or a user preference) becomes the basis of media queries, and the initial value for the &lt;code&gt;html&lt;/code&gt; element&#39;s font size. It&#39;s just not always &lt;code&gt;16px&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point, it&#39;s worth noting the consequences of absolute units for the &lt;code&gt;html&lt;/code&gt; font size. Using &lt;code&gt;html { font-size: 12px; }&lt;/code&gt; forces a size on the page that &lt;em&gt;outright&lt;/em&gt; ignores the user preference.&lt;/p&gt;
&lt;p&gt;In addition to being insensitive to the user, we further (and unpredictably) detach the notion of &lt;code&gt;1em&lt;/code&gt; in media queries — which, remember, are still based on that preference — from what&#39;s actually displayed on the page.&lt;/p&gt;
&lt;p&gt;Rather, think about it in terms of:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do I want my page, as a whole, to be typeset larger/smaller than, or largely the same as, the average experience the user has?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;...and then use font-relative units for the &lt;code&gt;html&lt;/code&gt; font size to express it. These units &lt;em&gt;tweak&lt;/em&gt; the user preference rather than dismiss it altogether.&lt;/p&gt;
&lt;h4&gt;Setting a minimum font size&lt;/h4&gt;
&lt;p&gt;As a supplement to the default font size, browsers can also impose a minimum font size.&lt;/p&gt;
&lt;p&gt;This does not normally¹ affect the initial font size. The font-relative media queries and &lt;code&gt;font-size&lt;/code&gt; declarations still use the initial font size as the basis. But at render time, text on the page will have the minimum baked in, and possibly display larger than we typeset it.&lt;/p&gt;
&lt;p&gt;When the minimum font size kicks in, browsers behave slightly differently. In Firefox, style declarations other than &lt;code&gt;font-size&lt;/code&gt; using &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;rem&lt;/code&gt; remain unaffected — they use the original computed size, before the adjustment. Chrome and Safari, on the other hand, will trickle the font size adjustment to other properties as well, so for example a padding of &lt;code&gt;1em&lt;/code&gt; around a text will remain proportional if the size is increased as a result of the minimum font size.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;¹ Safari comes with a single setting called &lt;em&gt;never use font sizes smaller than X&lt;/em&gt;. It gets factored into the initial font size, affecting media queries in ways I can&#39;t quite make heads and tails of, so... it&#39;s left as an exercise to the reader? :-)&lt;/p&gt;
&lt;h3&gt;Text-only zoom&lt;/h3&gt;
&lt;p&gt;Firefox has a &lt;em&gt;Zoom text only&lt;/em&gt; feature which alters the way zoom works. It disables the scaling of &lt;em&gt;CSS pixels&lt;/em&gt;, and instead factors the zoom level into the initial font size.&lt;/p&gt;
&lt;p&gt;That means that media queries using font-relative units (&lt;code&gt;em&lt;/code&gt;, &lt;code&gt;rem&lt;/code&gt;) get a new basis, matching the initial font size of the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;To really drive the feature home, and make it work as expected on pages which might use an &lt;em&gt;absolute&lt;/em&gt; font size on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element, it also factors the zoom level into the &lt;code&gt;font-size&lt;/code&gt; computed value.&lt;/p&gt;
&lt;p&gt;Everything works splendidly. The big losers here are &lt;code&gt;px&lt;/code&gt; queries. When you zoom in, the content gets bigger and bigger, and nothing changes in queryland, since there&#39;s no scaling of &lt;em&gt;CSS pixels&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;One less reason to ever use them!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Some takeaways from this foray into media queries:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;px&lt;/code&gt;-based media queries have little connection to the content displayed on the page, and are best avoided. They also fail to respond to the user&#39;s preferences about font size, and can&#39;t handle Firefox&#39;s text-only zoom.&lt;/p&gt;
&lt;p&gt;When it comes to font-relative CSS units, your best bet for predictable cross-browser behavior is to use &lt;code&gt;em&lt;/code&gt; in media queries. It avoids the problem with &lt;code&gt;rem&lt;/code&gt; in desktop Safari, but otherwise they&#39;re interchangeable. Zoom out of the page in macOS Safari to check that it does not break.&lt;/p&gt;
&lt;p&gt;When adjusting the font size on the &lt;code&gt;html&lt;/code&gt; element, use font-relative units to respect the user&#39;s preferences. And keep in mind that by changing it from the default, you&#39;re &lt;em&gt;slightly&lt;/em&gt; shifting the meaning of &lt;code&gt;1rem&lt;/code&gt; in styles vs. &lt;code&gt;1rem&lt;/code&gt; / &lt;code&gt;1em&lt;/code&gt; in media queries.&lt;/p&gt;
&lt;p&gt;It&#39;s hard to obtain an intuition on the way zoom levels and user preferences interact with one another, and the truth is always in the pudding. So be sure to test your page in a variety of scenarios involving different browsers, zoom levels, and font settings, where available.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to Simon for corrections &amp;amp; guidance in navigating the W3C specs.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Appendix: Deprecated media queries&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/2017/CR-mediaqueries-4-20170905/#mf-deprecated&quot;&gt;CSS Media Queries 4&lt;/a&gt; deprecates the use of &lt;code&gt;device-width&lt;/code&gt;, &lt;code&gt;device-height&lt;/code&gt;, and &lt;code&gt;device-aspect-ratio&lt;/code&gt;, which previously referred to physical pixels. Instead, browsers should &lt;a href=&quot;https://drafts.csswg.org/cssom-view-1/#web-exposed-screen-area&quot;&gt;start reporting them in CSS pixels&lt;/a&gt;, which Firefox has already started doing (Edge seems to do so as well, but I can&#39;t tell exactly in Browserstack). Safari and Chrome continue to report physical pixels at the time of writing.&lt;/p&gt;
&lt;p&gt;To CSS authors, these queries are not recommended.&lt;/p&gt;
&lt;h2&gt;Appendix: Methodology&lt;/h2&gt;
&lt;p&gt;I made a diagnostics page to observe what information browsers expose to CSS and JavaScript APIs:&lt;/p&gt;
&lt;p&gt;👉 &lt;a href=&quot;https://danburzo.ro/demos/browser-summary/&quot;&gt;Browser Summary&lt;/a&gt; 👈&lt;/p&gt;
&lt;p&gt;So far I have looked at the browsers I had at hand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firefox macOS&lt;/li&gt;
&lt;li&gt;Chrome macOS&lt;/li&gt;
&lt;li&gt;Safari macOS 12 &amp;amp; 13 (Technical Preview)&lt;/li&gt;
&lt;li&gt;Safari iOS 13&lt;/li&gt;
&lt;li&gt;Safari iPadOS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, I&#39;ve used Browserstack to check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Internet Explorer 11&lt;/li&gt;
&lt;li&gt;Microsoft Edge (I couldn&#39;t find a way to change the default font settings from within Edge itself, so I&#39;m not 100% sure about their impact)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Measuring Media Queries&lt;/h3&gt;
&lt;p&gt;Where do the values for &lt;code&gt;min-width&lt;/code&gt;, &lt;code&gt;min-height&lt;/code&gt;, et cetera come from on the diagnostics page?&lt;/p&gt;
&lt;p&gt;JavaScript has access to media queries via the CSS Object Model API. One particular feature is we can match media queries from JavaScript using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia&quot;&gt;&lt;code&gt;Window.matchMedia()&lt;/code&gt;&lt;/a&gt; method:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(min-width: 10rem)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows us to check &lt;code&gt;min-width&lt;/code&gt; against a certain value that matters to us, and see if it matches or not. But, for the diagnostics page, I needed to find the actual breakpoint beyond which a query (e.g. &lt;code&gt;min-width&lt;/code&gt;) stops matching — that is, the reverse of what &lt;code&gt;matchMedia()&lt;/code&gt; was designed for.&lt;/p&gt;
&lt;p&gt;Technically, the &lt;a href=&quot;https://drafts.csswg.org/cssom-view/#extensions-to-the-window-interface&quot;&gt;CSSOM View Module spec&lt;/a&gt; adds JS-accessible proxies for various measurements, but just to rule out possible inconsistencies in how browsers report these values vs. the actual pivotal points in media queries, I opted to obtain the numbers straight from the proverbial horse&#39;s mouth.&lt;/p&gt;
&lt;p&gt;To do that, we can (ab)use &lt;code&gt;matchMedia&lt;/code&gt; to learn the breakpoint of our current browser/device by asking repeatedly with different values. I used &lt;a href=&quot;https://en.wikipedia.org/wiki/Bisection_method&quot;&gt;the bisection method&lt;/a&gt; to avoid making a gazillion queries:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;find_min_width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 0 px&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1 million px&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; precision &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// whole pixels&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; precision&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; midpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;(min-width: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;midpoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; midpoint&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			end &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; midpoint&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function returns the breakpoint value, in pixels, of the &lt;code&gt;min-width&lt;/code&gt; media query for our current environment. The same technique, with some adjustments to the precision, can be used for &lt;code&gt;em&lt;/code&gt;, &lt;code&gt;rem&lt;/code&gt;, and &lt;code&gt;vw&lt;/code&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>A CSS-only layout debugger</title>
		<link href="https://danburzo.ro/css-layout-debugger/" />
		<updated>2019-09-16T00:00:00Z</updated>
		<id>https://danburzo.ro/css-layout-debugger/</id>
		<content type="html"
			>&lt;p&gt;I&#39;ve recently come across Gajus Kuizinas&#39; &lt;a href=&quot;https://dev.to/gajus/my-favorite-css-hack-32g3&quot;&gt;Favorite CSS hack&lt;/a&gt;. I had used basic CSS debug styles (also known as &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2007/09/07/diagnostic-styling/&quot;&gt;diagnostic CSS&lt;/a&gt;) before, and kind of love their simplicity. I wondered if I could take them up a notch. The challenge?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a CSS-only element inspector&lt;/strong&gt;: as you hover elements, show their bounding box, and how their descendants are laid out — all with minimal disruption to the layout.&lt;/p&gt;
&lt;h2&gt;The Approach&lt;/h2&gt;
&lt;p&gt;You can see what I came up with &lt;a href=&quot;https://danburzo.ro/demos/css-only-layout-debugger.html&quot;&gt;on this demo page&lt;/a&gt;, and follow along as I go into the thought process, the dead-ends, and interesting tidbits of CSS I learned in the process.&lt;/p&gt;
&lt;h3&gt;Basic styling&lt;/h3&gt;
&lt;p&gt;To show an element&#39;s box, we can &lt;code&gt;outline&lt;/code&gt; it:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;outline&lt;/code&gt; property does not alter the layout since it&#39;s painted on top of elements, and authors usually only include outline styles for focused elements, so it&#39;s relatively innocuous.&lt;/p&gt;
&lt;p&gt;To discern how the elements are nested, let&#39;s also add &lt;a href=&quot;https://en.wikipedia.org/wiki/Onionskin&quot;&gt;onionskin&lt;/a&gt; backgrounds:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far so good! This is starting to look promising, but everything is quite red.&lt;/p&gt;
&lt;h3&gt;Cycling the hue&lt;/h3&gt;
&lt;p&gt;To further distinguish elements at different levels of nesting, let&#39;s vary the outline / background color (as in &lt;a href=&quot;https://dev.to/gajus/my-favorite-css-hack-32g3&quot;&gt;Gajus&#39; example&lt;/a&gt;). The &lt;code&gt;hsl()&lt;/code&gt; notation, which is one of the few ways to obtain color variations in CSS right now, is a good candidate for expressing our colors.&lt;/p&gt;
&lt;p&gt;First, a bit of refactoring, to bring &lt;code&gt;red&lt;/code&gt; and &lt;code&gt;rgba(255, 0, 0, 0.1)&lt;/code&gt; together:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* red */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;--hue&lt;/code&gt; custom property in place, and the outline and background based on it, we can simply assign various values to it and have everything change harmoniously.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; HSL is good, but not &lt;em&gt;great&lt;/em&gt;. It replaces the machine-oriented red, green, and blue channels with something humans can better relate to — hue, saturation, and lightness. It&#39;s not, however, very true to how we perceive colors. Maintaining a constant saturation and lightness, colors of various hues will look wildly brighter or darker to the human eye. When it gets implemented in browsers, &lt;a href=&quot;https://www.w3.org/TR/css-color-4/#lab-colors&quot;&gt;the &lt;code&gt;lch()&lt;/code&gt; color notation&lt;/a&gt; will offer a better approximation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How do we go about cycling the hue in, let&#39;s say, 60 degree increments? You may be thinking, as I did, that CSS custom properties work like normal variables in other programming languages, and reach for:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; + 60&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, CSS disallows cyclic dependencies between custom properties — sets of properties that define themselves in terms of one another — and that includes a property that refers to itself. In a future version of the spec we might &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/1594#issuecomment-382832667&quot;&gt;get a way&lt;/a&gt; to let an element redefine a custom property based on the value it has inherited from its ancestors, but until then, no dice.&lt;/p&gt;
&lt;p&gt;Leafing through the &lt;a href=&quot;https://www.w3.org/TR/css-values-4/&quot;&gt;CSS values and units spec&lt;/a&gt;, I was surprised to find &lt;a href=&quot;https://www.w3.org/TR/css-values-4/#toggle-notation&quot;&gt;the &lt;code&gt;toggle()&lt;/code&gt; function&lt;/a&gt;. It enables elements to cycle over a set of values instead of inheriting the value. You&#39;d be able to write:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 60&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 120&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 180&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 240&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 300&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...to get elements at each subsequent level to move 60 degrees away from their parent. Nice and clear and... unsupported. Even though the &lt;code&gt;toggle()&lt;/code&gt; idea has been floating around &lt;a href=&quot;http://lists.w3.org/Archives/Public/www-style/1999May/0067&quot;&gt;since as early as 1999&lt;/a&gt;, no browsers implement it at the time of writing.&lt;/p&gt;
&lt;p&gt;So, unless I&#39;m missing a clever workaround, we&#39;re back to old-school:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 60&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 120&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 180&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 240&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 300&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup caps out at the 300 hue (a nice fuchsia), but you can continue to cycle it for as many &lt;code&gt;* &amp;gt; *&lt;/code&gt; selectors as your heart lets you.&lt;/p&gt;
&lt;h3&gt;Showing the padding around elements&lt;/h3&gt;
&lt;p&gt;If the browser barely broke a sweat from anything we did so far, it&#39;s time to raise the temperature with our next mini-challenge: showing padding visually.&lt;/p&gt;
&lt;p&gt;We&#39;re going to need something that lets us hook into the element&#39;s content box, and its padding box, independently. Hello &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/background-origin&quot;&gt;&lt;code&gt;background-origin&lt;/code&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;background-origin&lt;/code&gt; property is only meant for background &lt;em&gt;images&lt;/em&gt;, not background colors, so we need a way to make a background image out of a solid color. The &lt;a href=&quot;https://drafts.csswg.org/css-images-4/&quot;&gt;CSS Images Level 4&lt;/a&gt; spec defines &lt;a href=&quot;https://drafts.csswg.org/css-images-4/#image-notation&quot;&gt;the &lt;code&gt;image()&lt;/code&gt; syntax&lt;/a&gt; which accepts a color, such as &lt;code&gt;image(fuchsia)&lt;/code&gt;, to produce an image. But until browsers support it, we need to improvise with gradients.&lt;/p&gt;
&lt;p&gt;The shortest formula to get a solid color with the gradient syntax is, as far as I know:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fuchsia&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fuchsia&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get different colors for the content box and the padding box, we layer two of these images and define their &lt;code&gt;background-origin&lt;/code&gt; separately:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fuchsia&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fuchsia&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;yellow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; yellow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-repeat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The order of multiple backgrounds is from closest to the user (topmost) to furthest. In the code above, the content-bound fuchsia sits on top of the padding-bound yellow. I never remember this order and have to look it up constantly. It&#39;s important to include &lt;code&gt;background-repeat: no-repeat&lt;/code&gt; for it to work as expected, which I also tend to forget.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can probably get a similar effect with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip&quot;&gt;&lt;code&gt;background-clip&lt;/code&gt;&lt;/a&gt; instead of the &lt;code&gt;background-origin&lt;/code&gt; / &lt;code&gt;background-repeat&lt;/code&gt; combo, but I haven&#39;t looked into it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For a cooler look, like the one you sometimes see in dev tools, let&#39;s turn our yellow padding box into nice diagonal stripes. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/repeating-linear-gradient&quot;&gt;&lt;code&gt;repeating-linear-gradient&lt;/code&gt;&lt;/a&gt; is useful for this:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		45deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* angle for diagonals */&lt;/span&gt; fuchsia&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		fuchsia 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* our 1px stripes */&lt;/span&gt; transparent 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		transparent 3px &lt;span class=&quot;token comment&quot;&gt;/* 2px space between stripes */&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we work it into our inspector code:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/* Opaque version of the color */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--c-solid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/* Translucent version of the color */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--c-bg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 
		
		&lt;span class=&quot;token comment&quot;&gt;/* Content box fill */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Content box white underpaint */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;white&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; white&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* Padding box stripes */&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;45deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
						--c-bg
					&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 3px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-repeat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make the combo work with stripes, you may notice we&#39;ve added a coat of white paint over the stripes along the context box, to obscure them.&lt;/p&gt;
&lt;h3&gt;Making our inspector hover-aware&lt;/h3&gt;
&lt;p&gt;So far we&#39;ve done a decent job of highlighting everything on the page. It would be easier to follow if we could localize the highlights to the element we&#39;re hovering.&lt;/p&gt;
&lt;p&gt;Small problem: when you hover an element on the page, you don&#39;t just hover that element. You also hover all its ancestry. And the way CSS is designed does not allow us to select just the innermost hovered element. (We would need to know when a particular element contains another hovered element.)&lt;/p&gt;
&lt;p&gt;The closest we can get to the ideal is to change &lt;code&gt;*&lt;/code&gt; (every element on the page) to &lt;code&gt;body :hover, body :hover &amp;gt; *&lt;/code&gt;, which means &lt;em&gt;hovered elements and their direct descendants&lt;/em&gt;. We&#39;re also not styling &lt;code&gt;body&lt;/code&gt; itself, since it doesn&#39;t provide too much information and its add visual noise.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;body :hover,
body :hover &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/* Opaque version */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--c-solid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/* Translucent version */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--c-bg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 
		
		&lt;span class=&quot;token comment&quot;&gt;/* Content box fill */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Content box white underpaint */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;white&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; white&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* Padding box stripes */&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;45deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
						--c-bg
					&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 3px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-repeat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;A note on &lt;code&gt;:focus&lt;/code&gt;.&lt;/strong&gt; Unlike &lt;code&gt;:hover&lt;/code&gt;, only one element at a time has &lt;code&gt;:focus&lt;/code&gt;, and we could model our approach around that. However, to make all elements focusable, and avoid triggering their default behavior when we click on them, we would need the help of some JavaScript:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;tabindex&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...which takes away from the beauty of a CSS-only solution.&lt;/p&gt;
&lt;h3&gt;Extra credits: identifying the elements&lt;/h3&gt;
&lt;p&gt;One last mini-challenge: could we show information about elements — their tag name, ID and classes – as we hover them?&lt;/p&gt;
&lt;p&gt;CSS has &lt;a href=&quot;https://www.w3.org/TR/css-values-4/#attr-notation&quot;&gt;the &lt;code&gt;attr()&lt;/code&gt; function&lt;/a&gt; to read attributes from HTML elements and use them in styles. At the time of writing, we can only use &lt;code&gt;attr()&lt;/code&gt; as the &lt;code&gt;content&lt;/code&gt; of &lt;code&gt;::before&lt;/code&gt;and &lt;code&gt;::after&lt;/code&gt; pseudo-elements, but for our modest goals it will do nicely. We can extract an element&#39;s &lt;code&gt;class&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; — but not its tag name — and display them as a floating label:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;[id]:hover::before,
[class]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; -100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #000&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 80%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;:not([id])[class]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;:not([class])[id]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;[id][class]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s unpack that.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;[id]:hover::before, [class]:hover::before&lt;/code&gt; selector matches the &lt;code&gt;::before&lt;/code&gt; pseudo-element of hovered elements which have either an ID or a class attribute attached to them.&lt;/p&gt;
&lt;p&gt;Because we can&#39;t set these elements&#39; &lt;code&gt;content&lt;/code&gt; conditionally (based on which of the ID / class attributes are present) we set it for elements which have both (&lt;code&gt;[id][class]&lt;/code&gt;), and separately for ones which have just one (&lt;code&gt;:not([id])[class]&lt;/code&gt;) or the other (&lt;code&gt;:not([class])[id]&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;position: absolute&lt;/code&gt; takes the &lt;code&gt;::before&lt;/code&gt; pseudo-element out of the normal flow. By not specifying &lt;code&gt;top&lt;/code&gt; and &lt;code&gt;left&lt;/code&gt; offsets we leave it in its default place at the top-left hand corner of its parent&#39;s content box. Instead, we use &lt;code&gt;transform(0, -100%)&lt;/code&gt; to pull it upwards, so it sits on top of its parent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since we&#39;re taking over the &lt;code&gt;::before&lt;/code&gt; element, we might break aspects of the layout that may depend on it. And even so, the technique is not bulletproof, and might benefit from setting &lt;code&gt;position: relative&lt;/code&gt; on the parent and explicit &lt;code&gt;top&lt;/code&gt; and &lt;code&gt;left&lt;/code&gt; offsets, with the risk of further altering the original layout.&lt;/p&gt;
&lt;h3&gt;All together now&lt;/h3&gt;
&lt;p&gt;Here is the final version of the code:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* 
	Hue rotation 
	------------
*/&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 60&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 120&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 180&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 240&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 300&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 60&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 120&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 180&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 240&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;* &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; * &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--hue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 300&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 
	Draw elements&#39; boxes
	--------------------
*/&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;body :hover,
body :hover &gt; *&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/* Opaque version */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--c-solid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/* Translucent version */&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;--c-bg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 
		
		&lt;span class=&quot;token comment&quot;&gt;/* Content box fill */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;/* Content box white underpaint */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;white&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; white&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* Padding box stripes */&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;45deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-solid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
						--c-bg
					&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--c-bg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 3px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token property&quot;&gt;background-origin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content-box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background-repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-repeat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 
	Show elements&#39; classes / ID
	---------------------------
*/&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;[id]:hover::before,
[class]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; -100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #000&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hsl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--hue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 80%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;:not([id])[class]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;:not([class])[id]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;[id][class]:hover::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;To make it more resilient, we can sprinkle some &lt;code&gt;!important&lt;/code&gt; keywords, but that&#39;s left as an exercise to the reader.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&#39;s &lt;a href=&quot;https://danburzo.ro/demos/css-only-layout-debugger.html&quot;&gt;the demo page again&lt;/a&gt;. That is all! ✌️&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2018</title>
		<link href="https://danburzo.ro/favorite-records-2018/" />
		<updated>2018-12-12T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2018/</id>
		<content type="html"
			>&lt;ul&gt;
&lt;li&gt;Abul Mogard - Above All Dreams&lt;/li&gt;
&lt;li&gt;Against All Logic - 2012 - 2017&lt;/li&gt;
&lt;li&gt;Alva Noto - Unieqav&lt;/li&gt;
&lt;li&gt;Amen Dunes - Freedom&lt;/li&gt;
&lt;li&gt;Amnesia Scanner - Another Life&lt;/li&gt;
&lt;li&gt;Aphex Twin - Collapse EP&lt;/li&gt;
&lt;li&gt;Autechre - NTS Sessions 1&lt;/li&gt;
&lt;li&gt;AWVFTS - Long it May Sustain&lt;/li&gt;
&lt;li&gt;Balmorrhea - Clear Language (Reworked)&lt;/li&gt;
&lt;li&gt;Beak&amp;gt; - &amp;gt;&amp;gt;&amp;gt;; LA Playback&lt;/li&gt;
&lt;li&gt;Biosphere - The Hilvarenbeek Recordings&lt;/li&gt;
&lt;li&gt;Blawan - Wet Will Always Dry&lt;/li&gt;
&lt;li&gt;Blanck Mass - Odd Scene / Shit Luck&lt;/li&gt;
&lt;li&gt;Brian Eno &amp;amp; Kevin Shields - The Weight of History / Only Once Away my Son&lt;/li&gt;
&lt;li&gt;Bruce Brubaker - Codex&lt;/li&gt;
&lt;li&gt;Bruno Sanfilippo - Unity&lt;/li&gt;
&lt;li&gt;Bucharest - Budapest&lt;/li&gt;
&lt;li&gt;Chilly Gonzalez - Piano Solo III&lt;/li&gt;
&lt;li&gt;Daniel Avery - Song for Alpha&lt;/li&gt;
&lt;li&gt;Daniel Bjarnasson - Collider; Under the Tree&lt;/li&gt;
&lt;li&gt;David August - D&#39;Angelo; DCXXXIX A.C.&lt;/li&gt;
&lt;li&gt;David Bryne - American Utopia&lt;/li&gt;
&lt;li&gt;Deafhaven - Ordinary Corrupt Human Love&lt;/li&gt;
&lt;li&gt;Dead Can Dance - Dionysus&lt;/li&gt;
&lt;li&gt;Demdike Stare - Passion&lt;/li&gt;
&lt;li&gt;Dita von Teese - Remix&lt;/li&gt;
&lt;li&gt;☞ Dirtmusic - Bu Bir Ruya&lt;/li&gt;
&lt;li&gt;Donato Dozzy - Filo Loves the Acid&lt;/li&gt;
&lt;li&gt;Driftmachine - Shunter&lt;/li&gt;
&lt;li&gt;Dustin O&#39;Halloran - The Hate U Give OST; Puzzle OST&lt;/li&gt;
&lt;li&gt;EELS - The Deconstruction&lt;/li&gt;
&lt;li&gt;Eric Whitacre - Deep Field&lt;/li&gt;
&lt;li&gt;Federico Albanese - By the Deep Sea&lt;/li&gt;
&lt;li&gt;Fever Ray - Mustn&#39;t Hurry (remixes)&lt;/li&gt;
&lt;li&gt;Film School - Bright to Death&lt;/li&gt;
&lt;li&gt;Gas - Rausch&lt;/li&gt;
&lt;li&gt;Gazelle Twin - Pastoral&lt;/li&gt;
&lt;li&gt;Grouper - Field of Points&lt;/li&gt;
&lt;li&gt;GusGus - Lies are More Flexible&lt;/li&gt;
&lt;li&gt;Haroumi Hosono - NAGA&lt;/li&gt;
&lt;li&gt;Ian William Craig - Thresholder&lt;/li&gt;
&lt;li&gt;Ilya Beshevli - Deja Vu&lt;/li&gt;
&lt;li&gt;Interpol - Marauder&lt;/li&gt;
&lt;li&gt;Jacques Greene - Fever Focus&lt;/li&gt;
&lt;li&gt;Joep Beving - Conatus&lt;/li&gt;
&lt;li&gt;Jóhann Jóhannsson - Mandy OST; The Mercy OST&lt;/li&gt;
&lt;li&gt;Jon Hopkins - Singularity&lt;/li&gt;
&lt;li&gt;Josh T. Pearson - The Straight Hits!&lt;/li&gt;
&lt;li&gt;Julia Holter - Aviary&lt;/li&gt;
&lt;li&gt;Kenneth James Gibson - In the Fields of Nothing&lt;/li&gt;
&lt;li&gt;Khruangbin - Como Todo el Mundo&lt;/li&gt;
&lt;li&gt;Laurel Halo - Raw Silk Uncut Wood&lt;/li&gt;
&lt;li&gt;Lubomyr Melnyk - Fallen Trees&lt;/li&gt;
&lt;li&gt;Marianne Faithfull - Negative Capability&lt;/li&gt;
&lt;li&gt;Marissa Nadler - For my Crimes&lt;/li&gt;
&lt;li&gt;Mark Barrott - Nature Sounds of the Balearics&lt;/li&gt;
&lt;li&gt;Max Würden - Momentum&lt;/li&gt;
&lt;li&gt;Mogwai - Kin OST&lt;/li&gt;
&lt;li&gt;Niklas Paschburg - Oceanic&lt;/li&gt;
&lt;li&gt;Nils Frahm - All Melody; Encores 1&lt;/li&gt;
&lt;li&gt;Nine Inch Nails - Bad Witch&lt;/li&gt;
&lt;li&gt;Okkervil River - In the Rainbow Rain&lt;/li&gt;
&lt;li&gt;Oneohtrix Point Never - Age Of&lt;/li&gt;
&lt;li&gt;Oscar Mulero - Electric Shades EP&lt;/li&gt;
&lt;li&gt;OZmotic - Elusive Balance&lt;/li&gt;
&lt;li&gt;Peter Bjorn and John - Darker Days&lt;/li&gt;
&lt;li&gt;Phosphorescent - C&#39;Est la Vie&lt;/li&gt;
&lt;li&gt;Rhye - Blood; Blood Remixed&lt;/li&gt;
&lt;li&gt;Roger Eno - Dust of Stars&lt;/li&gt;
&lt;li&gt;Roman Flügel - Themes&lt;/li&gt;
&lt;li&gt;Ryuichi Sakamoto - Async Remodels&lt;/li&gt;
&lt;li&gt;Sevdaliza - The Calling&lt;/li&gt;
&lt;li&gt;Sigur Rós - Route One&lt;/li&gt;
&lt;li&gt;Slow Meadow - Screensaver Prelude&lt;/li&gt;
&lt;li&gt;Soundwalk Collective - Death Must Die&lt;/li&gt;
&lt;li&gt;Spiritualized - And Nothing Hurt&lt;/li&gt;
&lt;li&gt;Stuart A. Staples - Arrythmia&lt;/li&gt;
&lt;li&gt;Taylor Deupree - Fallen&lt;/li&gt;
&lt;li&gt;The Black Dog - Black Daisy Wheel; Post-Truth&lt;/li&gt;
&lt;li&gt;The Blaze - Dancehall&lt;/li&gt;
&lt;li&gt;The Brian Jonestown Massacre - Something Else&lt;/li&gt;
&lt;li&gt;The Field - Infinite Moment&lt;/li&gt;
&lt;li&gt;The Growlers - Casual Acquaintances&lt;/li&gt;
&lt;li&gt;The Limiñanas - I&#39;ve got trouble in mind; Shadow People&lt;/li&gt;
&lt;li&gt;This Will Destroy You - New Others Part One; Part Two&lt;/li&gt;
&lt;li&gt;Thom Yorke - Suspiria&lt;/li&gt;
&lt;li&gt;Tigran Hamasyan - For Gyumri EP&lt;/li&gt;
&lt;li&gt;Tim Hecker - Konoyo&lt;/li&gt;
&lt;li&gt;uZiq - Challenge Me Foolish&lt;/li&gt;
&lt;li&gt;V.A. - Climax OST&lt;/li&gt;
&lt;li&gt;V.A. - Liminal&lt;/li&gt;
&lt;li&gt;V.A. - In Death&#39;s Dream Kingdom&lt;/li&gt;
&lt;li&gt;Vanessa Wagner - Liszt, Pärt&lt;/li&gt;
&lt;li&gt;Vessel - Queen of Golden Dogs&lt;/li&gt;
&lt;li&gt;WhoMadeWho - Through the Walls&lt;/li&gt;
&lt;li&gt;Young Fathers - Cocoa Sugar&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2017</title>
		<link href="https://danburzo.ro/favorite-records-2017/" />
		<updated>2017-12-20T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2017/</id>
		<content type="html"
			>&lt;p&gt;Here&#39;s this year&#39;s records, listed alphabetically, with favorites in bold.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Album&lt;/th&gt;
&lt;th&gt;Bandcamp / SoundCloud / YT&lt;/th&gt;
&lt;th&gt;Apple Music&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aldous Harding — Party&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/aldous-harding/sets/party-1444&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/party/1214279887&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alessandro Cortini — AVANTI&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/alessandrocortiniofficial/sets/avanti-6&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/avanti/1267405189&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alex Cameron — Forced Witness&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://alkcm.bandcamp.com/album/forced-witness&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/forced-witness/1250563294&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ANOHNI — Paradise EP&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://anohni.bandcamp.com/album/paradise&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/paradise-ep/1194887350&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anton Kubikov — Whatness&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://kubikov.bandcamp.com/album/whatness&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/whatness/1234950603&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Astrïd &amp;amp; Rachel Grimes — Through the Sparkle&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://gizehrecords.bandcamp.com/album/through-the-sparkle&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/through-the-sparkle/1257702684&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balmorhea — Clear Language&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://balmorhea.bandcamp.com/album/clear-language&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/clear-language/1252681306&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedouine — Bedouine&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/bedouine-deluxe/1312847102&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/bedouine-deluxe/1312847102&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ben Frost — The Centre Cannot Hold&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://benfrost.bandcamp.com/album/the-centre-cannot-hold&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/the-centre-cannot-hold/1267617451&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Björk — Utopia&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/utopia/1303711887&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blanck Mass — World Eater&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://blanckmass.bandcamp.com/album/world-eater&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/world-eater/1191862517&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blonde Redhead — 3 O&#39;Clock EP&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/3-oclock-ep/1204160203&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blondie — Pollinator&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/pollinator/1198810651&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brainwaltzera — Poly-Ana&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://futureislisteningmusic.bandcamp.com/album/brainwaltzera-poly-ana-2&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/poly-ana/1267331848&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brian Eno — Reflection&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/reflection/1176137149&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burial — Rodent&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://burial.bandcamp.com/album/rodent-hdb113&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/rodent-single/1280387338&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cameron Avery — Ripe Dreams, Pipe Dreams&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://cameronavery.bandcamp.com/album/ripe-dreams-pipe-dreams&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/ripe-dreams-pipe-dreams-deluxe-edition/1188024147&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chilly Gonzales &amp;amp; Jarvis Cocker — Room 29&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/room-29/1194280442&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Charlotte Gainsbourg — Rest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eRwgL_PrQYQ&amp;amp;list=PL-d2FrHULiHKb4I4YfjgJVMx1TEkLMfNW&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/rest/1269631968&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cigarettes After Sex — Cigarettes After Sex&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://cigarettesaftersex.bandcamp.com/album/cigarettes-after-sex&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/cigarettes-after-sex/1215408950&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Courtney Barnett &amp;amp; Kurt Vile — Lotta Sea Lice&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://courtneybarnettandkurtvile.bandcamp.com/album/lotta-sea-lice&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/lotta-sea-lice/1270169831&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daniele Luppi &amp;amp; Parquet Courts — Milano&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/danieleluppi/sets/milano-5&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/milano/1279328251&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delia Gonzalez — Horse Follows Darkness&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://deliagonzalez.bandcamp.com/album/horse-follows-darkness&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/horse-follows-darkness/1221430830&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dustin O&#39;Halloran — 3 Movements&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://1631recordings.bandcamp.com/album/3-movements&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/3-movements-single/1210078388&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eluvium — Shuffle Drones&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://eluvium.bandcamp.com/album/shuffle-drones&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/shuffle-drones/1298237250&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fatima Al Qadiri — Shaneera&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fatimaalqadiri.bandcamp.com/album/hdb110-shaneera&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/shaneera-ep/1282016852&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fever Ray — Plunge&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/fever-ray/sets/plunge-4&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/plunge/1298376912&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Four Tet — New Energy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fourtet.bandcamp.com/album/new-energy&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/new-energy/1288517633&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gas — Narkopop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://kompakt-gas.bandcamp.com/album/narkopop&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/narkopop/1212939308&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Godspeed You! Black Emperor — Luciferian Towers&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://godspeedyoublackemperor.bandcamp.com/album/luciferian-towers&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/luciferian-towers/1264190010&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grizzly Bear — Painted Ruins&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/painted-ruins/1235159880&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High Plains — Cinderland&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://highplainskranky.bandcamp.com/album/cinderland&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/cinderland/1186064831&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iron &amp;amp; Wine — Beast Epic&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://ironandwine.bandcamp.com/album/beast-epic&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/beast-epic/1238344396&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Japandroids — Near to the Wild Heart of Life&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://japandroids.bandcamp.com/album/near-to-the-wild-heart-of-life&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/near-to-the-wild-heart-of-life/1168964814&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jefre Cantu-Ledesma — On the Echoing Green&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://jefrecantu-ledesma.bandcamp.com/album/on-the-echoing-green&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/on-the-echoing-green/1213843979&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jlin — Black Origami&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://jlin.bandcamp.com/album/black-origami&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/black-origami/1213597238&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Joni Void — Selfless&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://jonivoid.bandcamp.com/album/selfless&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/selfless/1208268417&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Justin Walter — Unseen Forces&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://justinwalter.bandcamp.com/album/unseen-forces-2&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/unseen-forces/1207130602&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kastil — The Sadistic Abbess&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soulnotes.bandcamp.com/album/the-sadistic-abbess-st171&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/the-sadistic-abbess/1200874669&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kelly Lee Owens — Kelly Lee Owens&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://kellyleeowens.bandcamp.com/album/kelly-lee-owens&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/kelly-lee-owens/1195541100&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kendrick Lamar — DAMN.&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/kendrick-lamar-music/sets/damn-86&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/damn/1223618217&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;King Krule — The OOZ&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/kingkruleofficial/sets/the-ooz-1&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/the-ooz/1273304020&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kristoffer Lo — Anhedonia&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/kristofferloofficial/sets/anhedonia-6&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/anhedonia/1211343879&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lana Del Rey — Lust for Life&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/lana-del-rey/sets/lust-for-life-6&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/lust-for-life/1255937240&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laraaji — Sun Gong&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/all-saints-records/15_sun-gong-2-gong-sun-edit-by&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/sun-gong/1260592601&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD Soundsystem - american dream&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/lcd-soundsystem/sets/american-dream-11&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/american-dream/1258822744&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leandro Fresco &amp;amp; Rafael Anton Irisarri — La Equidistancia&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://astrangelyisolatedplace.bandcamp.com/album/la-equidistancia&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/la-equidistancia/1226211433&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lift to Experience — The Texas-Jerusalem Crossroads &lt;em&gt;(Reissue)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/lifttoexperience/sets/the-texas-jerusalem-crossroads&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/the-texas-jerusalem-crossroads/1172400599&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mdou Moctar — Sousoume Tamachek&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://mdoumoctar.bandcamp.com/album/sousoume-tamachek&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/sousoume-tamachek/1282451487&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mogwai — Every Country&#39;s Sun&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://temporaryresidence.bandcamp.com/album/every-countrys-sun&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/every-countrys-sun/1232654304&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Murcof &amp;amp; Vanessa Wagner — EP02 &amp;amp; EP03&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://infine-rec.bandcamp.com/album/ep-02&quot;&gt;EP02&lt;/a&gt;, &lt;a href=&quot;https://infine-rec.bandcamp.com/album/ep03&quot;&gt;EP03&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/ep02-single/1235207906&quot;&gt;EP02&lt;/a&gt;, &lt;a href=&quot;https://itunes.apple.com/ro/album/ep03-ep/1299863527&quot;&gt;EP03&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oscar Mulero — Pattern Series Compilation&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/oscarmulero/sets/oscar-mulero-pattern-series&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/pattern-series-compilation/1302916574&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oxbow — The Black Duke&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://oxbowofficial.bandcamp.com/album/thin-black-duke&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/thin-black-duke/1222102515&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ryuichi Sakamoto - async&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/ryuichi-sakamoto-official/sets/async-2&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/async/1218980002&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sevdaliza — Ison&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=znV9KDsNtXY&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/ison/1314724379&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;St. Vincent — MASSEDUCTION&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/st_vincent/sets/masseduction&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/masseduction/1276543346&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tale of Us — Endless&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/taleofus/sets/endless-18&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/endless/1206594945&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Acid — The Bomb OST&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/theacid/sets/the-bomb-original-motion&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/the-bomb-original-motion-picture-soundtrack/1293679751&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Blaze — Territory EP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLMalG3HUd0QQd0FT1ll32P9gp9k4cOlT0&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/territory-ep/1203811796&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The National — Sleep Well Beast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=GwZvip416NU&amp;amp;list=PLIrpDAtP87O5jypS5PGgHWCH6qCwR-efr&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/sleep-well-beast/1233837225&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The War on Drugs — A Deeper Understanding&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/thewarondrugs/sets/a-deeper-understanding-1&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/a-deeper-understanding/1242366660&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timber Timbre — Sincerely, Future Pollution&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://timbertimbre.bandcamp.com/album/sincerely-future-pollution&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/sincerely-future-pollution/1187851168&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tom Rogerson &amp;amp; Brian Eno — Finding Shore&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tomrogerson.bandcamp.com/album/finding-shore&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/finding-shore/1287106205&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible Cloaks — Reassemblage&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://visiblecloaks.bandcamp.com/album/reassemblage&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/reassemblage/1176716928&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Waxahatchee — Out in the Storm&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://waxahatchee.bandcamp.com/album/out-in-the-storm&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/out-in-the-storm-deluxe-version/1222703546&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;William Basinski — A Shadow in Time&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://williambasinski.bandcamp.com/album/a-shadow-in-time&quot;&gt;Bandcamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/a-shadow-in-time/1185573890&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yaeji — EP2&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/godmodemusic/sets/yaeji-ep2-godmode&quot;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/ro/album/ep2/1297894762&quot;&gt;🍎&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;A significant album I was introduced to this year is the 2012 &lt;em&gt;Changeling&lt;/em&gt; by Camille O&#39;Sullivan &lt;a href=&quot;https://itunes.apple.com/ro/album/changeling/1187095541&quot;&gt;🍎&lt;/a&gt;, which has been on heavy rotation throughout. It starts with a cover of Gillian Welch&#39;s &lt;strong&gt;Revelator&lt;/strong&gt;, about which I got excited &lt;a href=&quot;https://danburzo.ro/favorite-records-2016#addendum-dec-16-2016&quot;&gt;this time last year&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other lists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.roughtrade.com/gb/albums-of-the-year&quot;&gt;Rough Trade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://thequietus.com/articles/23660-albums-of-the-year-2017&quot;&gt;The Quietus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bleep.com/Albums-of-the-Year-2017&quot;&gt;Bleep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boomkat.com/charts/2017/558&quot;&gt;Boomkat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2016</title>
		<link href="https://danburzo.ro/favorite-records-2016/" />
		<updated>2016-12-06T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2016/</id>
		<content type="html"
			>&lt;p&gt;It&#39;s been a great year for soundtracks, ambient, and modern classical music. Here are my selections for 2016, ordered A to Z, narrowed down to fifty albums I&#39;ve listened to more closely and have enjoyed. My ten favorites are in bold.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Album&lt;/th&gt;
&lt;th&gt;♫&lt;/th&gt;
&lt;th&gt;🍏&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Adam Bryanbaum Wiltzie – Salero&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://erasedtapes.bandcamp.com/album/salero-original-motion-picture-soundtrack&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/CdbFeb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andy Stott – Too Many Voices&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLpOEGKO1FO736ksFZ6DpRyMSMVUzy-jB-&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/5gkXbb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ANOHNI – Hopelessness&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://anohni.bandcamp.com/album/hopelessness&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/y8t3ab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A Winged Victory for the Sullen – Iris&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://erasedtapes.bandcamp.com/album/iris-musique-originale-bonus-track-version&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/ip25fb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Biosphere – Departed Glories&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://biosphere.bandcamp.com/album/departed-glories&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/OyBUdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brian Eno – The Ship&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=pn1riJSHhkY&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/ZuVMab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Caretaker – Everywhere at the end of time&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://thecaretaker.bandcamp.com/album/everywhere-at-the-end-of-time&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Car Seat Headrest – Teens of Denial&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://carseatheadrest.bandcamp.com/album/teens-of-denial&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/t-xLcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circe – Circe&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/sigur-ros/sets/circe&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/SVaV8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Colin Stetson – Sorrow: A reimagining of Gorecki&#39;s 3rd Symphony&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8c7YvSMyIso&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Pb6tab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daniel Lanois – Goodbye to Language&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://daniellanois.bandcamp.com/album/goodbye-to-language&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/3B5sdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;David Bowie – ★&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=kszLwBaC4Sw&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/JB7h_&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Demdike Stare – Wonderland&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLpOEGKO1FO72NbJ_neMLDDIM-4hkYhtWH&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eluvium – False Readings On&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://eluvium.bandcamp.com/album/false-readings-on&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/jqWmdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explosions in the Sky – The Wilderness&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://explosionsinthesky.bandcamp.com/album/the-wilderness&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/pA64_&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Floating Points – Kuiper&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/floatingpoints/kuiper&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/F9vtcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gold Panda – Good Luck and Do Your Best&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/F9vtcb&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Pa8Qab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kassel Jaeger, Stephan Mathieu, Akira Rabelais – Zauberberg&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://schwebung.bandcamp.com/album/zauberberg&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/2PSvab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jambinai – A Hermitage&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FPRle456t88&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/AVkQab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jherek Bischoff – Cistern&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://jherekbischoff.bandcamp.com/album/cistern&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/TKDqcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Jóhann Jóhannsson – Orphée&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=AlftMNmDH00&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/HD0udb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jóhann Jóhannsson – Arrival&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=qsaRJ4j4xIo&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/OnUAfb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;King Ghazi Presents Abu Sayah – Houran &amp;amp; Shamaleh&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/versatile-records/sets/king-ghazi-presents-abu-sayah-houran-shamaleh&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Y3Wvcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kjartan Sveinsson – Der Klang der Offenbarung des Göttlichen&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MFiwD4LsbGw&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/eKYpfb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Klara Lewis – Too&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://editionsmego.bandcamp.com/album/too&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/bTsmbb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kristoffer Lo – The Black Meat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BhvkxdhDtS8&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/0dFRab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Limiñanas – Malamore&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://theliminanas.bandcamp.com/album/malamore&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/YEflbb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambchop – FLOTUS&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=DQMNeFnuyMU&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/qvEZdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loscil – Monument Builders&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://loscil.bandcamp.com/album/monument-builders&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/fm9yfb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lubomyr Melnyk – Illirion&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=NrNJBcvNjjA&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/TCSWbb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ludovico Einaudi – Elements (The Remixes) EP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UFLrP55hZqs&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/PSiEab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max Richter – Sleep (Remixes)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=uoiQTKOJr5M&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/zBKwab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Melanie de Biasio – Blackened Cities&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://melaniedebiasio.bandcamp.com/album/blackened-cities&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=uoiQTKOJr5M&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mogwai – Atomic&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://temporaryresidence.bandcamp.com/album/atomic&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/wmU9_&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Murcof × Vanessa Wagner – Statea&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://infine-rec.bandcamp.com/album/statea-cd-lp&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/aDJ4db&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nick Cave &amp;amp; the Bad Seeds – Skeleton Tree&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BAMZYpZi_M4&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/7-wPcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PJ Harvey – The Hope Six Demolition Project&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=qsLqsqbObyg&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/lO19_&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Praed – The Fabrication of Silver Dreams&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/annihaya-records/praed-pyramids-in-the-sky-fabrication-of-silver-dreams-2016&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/3Y1Cab&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Radiohead – A Moon Shaped Pool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=TTAU7lLDZYU&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/psvqcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Richard J. Birkin – Vigils&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/tomreveal/sets/richard-jbirkin-vigils-revealrecords&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/CF36_&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scott Walker – The Childhood of a Leader&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=drxpGP2r4UM&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/OPDfdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tindersticks – The Waiting Room&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tindersticks.bandcamp.com/album/the-waiting-room&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/J12t-&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trent Reznor &amp;amp; Atticus Ross – Juno&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/0yLsdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trent Reznor &amp;amp; Atticus Ross, Gustavo Santaolalla, Mogwai – Before the Flood&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/trentreznorandatticusross/sets/before-the-flood-soundtrack-1&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/u2Cufb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Various Artists – Eleven into Fifteen: a 130701 Compilation&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLSjc8Z5Z5KGC9uASkjATYiWK7BecNeMcP&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Lk_2fb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Various Artists – LateNightTales: Olafur Arnalds&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://latenighttales.bandcamp.com/album/late-night-tales-lafur-arnalds&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/vTx-bb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Various Artists ‐ Pop Ambient 2017&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://kompakt.bandcamp.com/album/pop-ambient-2017&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/U8Qefb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wolfgang Voigt – Ambient Grunge&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=yzG9GyFzVYI&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/ynqUeb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Woodkid &amp;amp; Nils Frahm – Ellis EP&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/erasedtapes/woodkid-nils-frahm-winter-morning-i&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/gxzQcb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yann Tiersen – EUSA&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://eusasound.bzh/&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/sPuQdb&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;Addendum, Dec 16, 2016&lt;/h3&gt;
&lt;p&gt;A great album that has cropped up since I&#39;ve published the list is &lt;a href=&quot;http://alkcm.bandcamp.com/album/jumping-the-shark&quot;&gt;Alex Cameron&#39;s &amp;quot;Jumping the Shark&amp;quot;&lt;/a&gt; (&lt;a href=&quot;https://itun.es/ro/ahWGcb&quot;&gt;🍏&lt;/a&gt;). Also worth noting is &lt;a href=&quot;https://www.youtube.com/watch?v=2roLC4yBmZU&quot;&gt;Sylvan Esso&#39;s cover of &amp;quot;Everything is Free&amp;quot;&lt;/a&gt; (&lt;a href=&quot;https://itun.es/ro/EgKzeb?i=1147552350&quot;&gt;🍏&lt;/a&gt;) by Gillian Welch, whose album &amp;quot;Time (The Revelator)&amp;quot; (&lt;a href=&quot;https://itun.es/ro/g6rWe&quot;&gt;🍏&lt;/a&gt;) was, well, a revelation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; This year&#39;s list in the form of &lt;a href=&quot;https://www.youtube.com/playlist?list=PL3jn6K6l-pAHKqDH2zMNPWs6yi2gV5c3m&quot;&gt;a glorious YouTube playlist&lt;/a&gt;, courtesy of my friend Adi.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other lists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.roughtrade.com/albums-of-the-year&quot;&gt;Rough Trade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://thequietus.com/articles/21429-albums-of-the-year-2016&quot;&gt;The Quietus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Extracting things from JavaScript strings</title>
		<link href="https://danburzo.ro/string-extract/" />
		<updated>2016-08-19T00:00:00Z</updated>
		<id>https://danburzo.ro/string-extract/</id>
		<content type="html"
			>&lt;p&gt;You want to pull some information out of a JavaScript string and you have a pattern (regular expression, that is) to match against. Surprised that the String/RegExp combo API lacks a direct method, you comb the web for a solution. On StackOverflow you&#39;re invited to put a &lt;code&gt;RegExp.exec()&lt;/code&gt; in a &lt;code&gt;while&lt;/code&gt; loop. I&#39;ll show you a better if unorthodox way of doing it with &lt;code&gt;String.replace&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;(Ab)using String.replace&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with the real-life-sounding, contrived scenario of wanting to &lt;em&gt;extract all numbers from a JavaScript string&lt;/em&gt;. A simple pattern for matching all occurrences of numbers (specifically, decimal and floating point numbers) is:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[+-]?&#92;d+(&#92;.&#92;d+)?&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(don&#39;t forget the &lt;code&gt;g&lt;/code&gt; flag for Global)&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;RegExp.exec&lt;/code&gt; way of doing it:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Some of the best numbers are 42, and, in particular 42.999.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; number_regex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[+-]?&#92;d+(&#92;.&#92;d+)?&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; matches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; number_regex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	matches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// =&gt; [&quot;42&quot;, &quot;42.999&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If instead we use &lt;code&gt;String.replace&lt;/code&gt; we can write:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Some of the best numbers are 42, and, in particular 42.999.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; number_regex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[+-]?&#92;d+(&#92;.&#92;d+)?&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; matches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number_regex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	matches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// =&gt; [&quot;42&quot;, &quot;42.999&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In effect, we&#39;re hijacking &lt;code&gt;String.replace&lt;/code&gt; to act as an iterator over the matches, rather than actually replacing anything in the String.&lt;/p&gt;
&lt;p&gt;This has a couple of advantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;you do away with the anxiety-inducing &lt;code&gt;while&lt;/code&gt; loop because what can go wrong I&#39;ll tell you what can go wrong; use a regular expression literal in the &lt;code&gt;while&lt;/code&gt; statement, instead of a regex saved in variable, and you&#39;ll get an infinite loop (courtesy of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex&quot;&gt;&lt;code&gt;lastIndex&lt;/code&gt; property&lt;/a&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[+-]?&#92;d+(&#92;.&#92;d+)?&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FOREVERCODE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;you get to have &lt;em&gt;named parameters&lt;/em&gt; for your matches instead indexes in an array.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Extracting parts of a pattern&lt;/h2&gt;
&lt;p&gt;Each group we create in a regular expression, using the &lt;code&gt;(...)&lt;/code&gt; construct, will result in an additional parameter to our &amp;quot;replacer&amp;quot; function. Let&#39;s take another example.&lt;/p&gt;
&lt;p&gt;Assume in an blog article you can add Wordpress-style shortcodes for embedding videos:&lt;/p&gt;
&lt;pre class=&quot;language-txt&quot;&gt;&lt;code class=&quot;language-txt&quot;&gt;Top 10 videos this months:

1. [youtube:FyCsJAj69sc]
2. [vimeo:128373915]
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A straightforward regular expression to match these shortcodes is:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;[&#92;w+:&#92;w+&#92;]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// match alphanumeric characters, followed by colon, &lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// followed by another set of alphanumeric characters&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// all wrapped in square brackets&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but we want to match the parts individually, so we put them in groups:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;[(&#92;w+):(&#92;w+)&#92;]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our pattern-extraction function now receives two extra parameters, one for each group:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Top 10 videos this months: &#92;
			1. [youtube:FyCsJAj69sc] &#92;
			2. [vimeo:128373915]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; shortcode_regex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;[(&#92;w+):(&#92;w+)&#92;]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; matches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shortcode_regex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;match&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	matches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; id
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For which we get:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;youtube&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FyCsJAj69sc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vimeo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;128373915&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Have a regex group that you don&#39;t want to show up in the matcher function? Use the non-capturing group syntax &lt;code&gt;(?: ... )&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Now you know how to make pattern extraction more readable and less error-prone.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>A few FACT mixes</title>
		<link href="https://danburzo.ro/fact-mixes/" />
		<updated>2016-01-22T00:00:00Z</updated>
		<id>https://danburzo.ro/fact-mixes/</id>
		<content type="html"
			>&lt;p&gt;Time for another listicle sans commentary!&lt;/p&gt;
&lt;p&gt;Here are a few of the weirder &lt;a href=&quot;http://www.factmag.com/category/factmixes/&quot;&gt;FACT magazine mixes&lt;/a&gt; — the ones to which I find myself getting back, time and time again.&lt;/p&gt;
&lt;h2&gt;#379 - Grouper (Apr 2013)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2013/04/22/fact-mix-379-grouper/&quot;&gt;Link&lt;/a&gt;. Tracklist N/A.&lt;/p&gt;
&lt;h2&gt;#399 - Zola Jesus (Sept 2013)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2013/09/09/fact-mix-399-zola-jesus/&quot;&gt;Link&lt;/a&gt;. Tracklist N/A.&lt;/p&gt;
&lt;h2&gt;#414 - Julianna Barwick (Dec 2013)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2013/12/02/fact-mix-414-julianna-barwick/&quot;&gt;Link&lt;/a&gt;. Tracklist N/A.&lt;/p&gt;
&lt;h2&gt;#445 - Stephen O&#39;Malley (Jun 2014)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2014/06/09/fact-mix-445-stephen-omalley/&quot;&gt;Link&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Tracklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Spiridon Shisgigin - ‘Tanz der Schamanin (Dance of the Female Shaman)’ (from Soul of Yakutia)&lt;/li&gt;
&lt;li&gt;Akira Rabelais - ‘Comme Un Ange Enivré D’un Soleil Radieux’ (from Caduceus)&lt;/li&gt;
&lt;li&gt;Hornroh - ‘MUEZZO’ (from Findling)&lt;/li&gt;
&lt;li&gt;Ernstalbrecht Stiebler - ‘…Im klang II’ (from «… Im klang…» hat(now))&lt;/li&gt;
&lt;li&gt;Angus MacLise - ‘Universal Solar Calendar’ (The Cloud Doctrine)&lt;/li&gt;
&lt;li&gt;Angus MacLise - ‘Tambura Drone + Sine Wave Generator’ (from The Cloud Doctrine)&lt;/li&gt;
&lt;li&gt;Moluk Zarrābi Darāmad - ‘Dād (Māhur)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)&lt;/li&gt;
&lt;li&gt;Montakhab-oz-Zākerin - ‘Qafqāz I (Segāh)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)&lt;/li&gt;
&lt;li&gt;Montakhab-oz-Zākerin - ‘Qafqāz II (Segāh)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)&lt;/li&gt;
&lt;li&gt;Irān-od-Dowleh Helen - ‘Darāmad, Dād, Khāvarān (Māhur)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)&lt;/li&gt;
&lt;li&gt;Hans Kennel, Mytha - ‘Chuehreiheli’ (from How It All Started)&lt;/li&gt;
&lt;li&gt;Susan Alcorn - ‘Sapphire’ (from New Music For Old Instruments)&lt;/li&gt;
&lt;li&gt;Christian Wolff - ‘Exercise 10’ (from 10 Exercises)&lt;/li&gt;
&lt;li&gt;Arthur Brown’s Kingdom Come - ‘Triangles’ (from Journey)&lt;/li&gt;
&lt;li&gt;Jacula - ‘Ego Sum Qui Sum’ (from Anno Demoni)&lt;/li&gt;
&lt;li&gt;Antonius Rex - ‘Enchanted Woods’ (from Ralefun)&lt;/li&gt;
&lt;li&gt;Fushitsusha - ‘暗号’ (from A Document Film Of Keiji Haino)&lt;/li&gt;
&lt;li&gt;Joe Meek/The Blue Men - ‘The Bulblight’ (from I hear a new world)&lt;/li&gt;
&lt;li&gt;François-Bernard Mâche - ‘Ouverture’ (from L’announce faite a marie)&lt;/li&gt;
&lt;li&gt;François-Bernard Mâche - ‘Le baiser’ (from L’announce faite a marie)&lt;/li&gt;
&lt;li&gt;François-Bernard Mâche - ‘Le vent’ (from L’announce faite a marie)&lt;/li&gt;
&lt;li&gt;François-Bernard Mâche - ‘Chevauchée’ (from L’announce faite a marie)&lt;/li&gt;
&lt;li&gt;François-Bernard Mâche - ‘Le pélerin’ (from L’announce faite a marie)&lt;/li&gt;
&lt;li&gt;François-Bernard Mâche - ‘Ma mise au tombeau’ (from L’announce faite a marie)&lt;/li&gt;
&lt;li&gt;Jakob Ullmann - ‘Disappearing Musics For Six Players (More Or Less)’ (from Edition Zeitgenössische Musik)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;#471 - Jonny Trunk (Nov 2014)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2014/11/20/fact-mix-470-jonny-trunk/&quot;&gt;Link&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Tracklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Guide To Oral Sex (unreleased)&lt;/li&gt;
&lt;li&gt;You – Mean&lt;/li&gt;
&lt;li&gt;Flugerenden – Unknown&lt;/li&gt;
&lt;li&gt;Hot Water – Hazlewood / Trey&lt;/li&gt;
&lt;li&gt;Pace – Golden Ring 6&lt;/li&gt;
&lt;li&gt;Screw On – Jacky Giordano&lt;/li&gt;
&lt;li&gt;The Sapphic Sleep – Don Cherry&lt;/li&gt;
&lt;li&gt;Ode To A Screw – Taking Off&lt;/li&gt;
&lt;li&gt;The Wizard – Frankie Bones&lt;/li&gt;
&lt;li&gt;Metallic Doings – Bruton Library&lt;/li&gt;
&lt;li&gt;Electro People – Fox&lt;/li&gt;
&lt;li&gt;Moogies Bloogies – Anthony Newley / Delia Derbyshire (unreleased)&lt;/li&gt;
&lt;li&gt;Shadows Of Blood – Platonos&lt;/li&gt;
&lt;li&gt;Eyes Without A Face – Jarre&lt;/li&gt;
&lt;li&gt;See Saw – Libby&lt;/li&gt;
&lt;li&gt;Smilin Billy – Heath Bros&lt;/li&gt;
&lt;li&gt;Even The Horses Had Wings – Kathy Bobo Bates&lt;/li&gt;
&lt;li&gt;Underwater Boy – Lindt&lt;/li&gt;
&lt;li&gt;Airlock – Herrmann&lt;/li&gt;
&lt;li&gt;Makkaresh – Torrossi&lt;/li&gt;
&lt;li&gt;Tornadeo – Jiants&lt;/li&gt;
&lt;li&gt;Ghost Horse – Elsie&lt;/li&gt;
&lt;li&gt;Sea Drift – Jonny Trunk (unreleased)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;#512 - Max Richter&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2015/09/07/fact-mix-512-max-richter/&quot;&gt;Link&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Tracklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Charles Ives – The Unanswered Question&lt;/li&gt;
&lt;li&gt;Godspeed You! Black Emperor – Rockets fall on Rocket falls&lt;/li&gt;
&lt;li&gt;J.S. Bach – Fugue in C# minor from Book 1 of The Well tempered Clavier&lt;/li&gt;
&lt;li&gt;J.S Bach – Chorale from “Christ Lag in Todesbanden”&lt;/li&gt;
&lt;li&gt;Osvaldo Golijov – Tenebrae II&lt;/li&gt;
&lt;li&gt;Urmas Sisask – Ursa Minor from Starry Sky Cycle&lt;/li&gt;
&lt;li&gt;Boards of Canada – Over the horizon radar&lt;/li&gt;
&lt;li&gt;Sergei Rachmaninov – Rejoice O Virgin from The Vespers&lt;/li&gt;
&lt;li&gt;Howard Skempton – Of Late&lt;/li&gt;
&lt;li&gt;Philip Glass – Violin Concerto 2nd Movement&lt;/li&gt;
&lt;li&gt;Grouper – Clearing&lt;/li&gt;
&lt;li&gt;Henry Purcell – Fantasia in 7 parts&lt;/li&gt;
&lt;li&gt;William Byrd – Mass for 5 voices, Agnus Dei&lt;/li&gt;
&lt;li&gt;Luciano Berio – Wasserklavier&lt;/li&gt;
&lt;li&gt;Michael Tippet – Concerto for Sting Orchestra, II Adagio Cantabile&lt;/li&gt;
&lt;li&gt;Cat Power – Maybe Not&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;#522 - Vainio &amp;amp; Vigroux&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2015/11/09/fact-mix-522-vainio-vigroux/&quot;&gt;Link&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Tracklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Giacinto Scelsi – Anahit&lt;/li&gt;
&lt;li&gt;Orchestra and choir of Polish radio&lt;/li&gt;
&lt;li&gt;Pharmakon – Crawling on bruised knees&lt;/li&gt;
&lt;li&gt;Napalm Death – Peel Sessions 88: tracks 1 to 10&lt;/li&gt;
&lt;li&gt;Bernard Parmegiani – Accidents Harmoniques – De Naturae Sonorum (GRM)&lt;/li&gt;
&lt;li&gt;Scott Walker – Clara&lt;/li&gt;
&lt;li&gt;Techno Animal – Hell&lt;/li&gt;
&lt;li&gt;Federico Schmucher – Print…?&lt;/li&gt;
&lt;li&gt;Whitehouse – Wriggle like a Fucking Eel&lt;/li&gt;
&lt;li&gt;Swans – Coward&lt;/li&gt;
&lt;li&gt;Robert de Visee – Courante&lt;/li&gt;
&lt;li&gt;Interprète Pascal Monteilhet (Orbe) – Zig Zag label&lt;/li&gt;
&lt;li&gt;Haino Keiji – First Blackness&lt;/li&gt;
&lt;li&gt;Disques Du Soleil Et De L’Acier&lt;/li&gt;
&lt;li&gt;Shapednoise feat. Justin K Broadrick – Enlightenment&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;#527 - Jóhann Jóhannsson (Dec 2015)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.factmag.com/2015/12/07/fact-mix-527-johann-johannsson/&quot;&gt;Link&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Tracklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Mihaly Vig: The Turin Horse&lt;/li&gt;
&lt;li&gt;Dimitri Shostakovich: String Quartet No. 15 in E Flat Minor Op. 144 – 1. Elegy, Adagio&lt;/li&gt;
&lt;li&gt;David Lang: Amelia (I’m Waiting for My Man)&lt;/li&gt;
&lt;li&gt;Meredith Monk: Braid 1 and Leaping Song&lt;/li&gt;
&lt;li&gt;Robert Turman: Flux&lt;/li&gt;
&lt;li&gt;Glenn Branca: Second Movement (The Temple of Venus Pt. 2)&lt;/li&gt;
&lt;li&gt;Gloria Coates: Chiaroscuro – Illumination&lt;/li&gt;
&lt;li&gt;Witold Lutoslawski: Funeral Music – Prologue&lt;/li&gt;
&lt;li&gt;Jón Leifs: Hinsta Kveðja (Elegy) Op. 53 for String Orchestra&lt;/li&gt;
&lt;li&gt;Irena Havlová &amp;amp; Vojtěch Havel: Pod Hvězdou Pomiranč (Under The Orange Star)&lt;/li&gt;
&lt;li&gt;Charlemagne Palestine: Sliding Fifths (1 2)&lt;/li&gt;
&lt;li&gt;Alvin Lucier: I Am Sitting in a Room&lt;/li&gt;
&lt;li&gt;Holger Czukay: Boat Woman Song&lt;/li&gt;
&lt;li&gt;Charlemagne Palestine: One Two Three Fifths (1 3)&lt;/li&gt;
&lt;/ol&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Drawing every street in Romania</title>
		<link href="https://danburzo.ro/every-street/" />
		<updated>2015-12-28T00:00:00Z</updated>
		<id>https://danburzo.ro/every-street/</id>
		<content type="html"
			>&lt;p&gt;Ben Fry&#39;s project &lt;a href=&quot;http://3rdfloor.fathom.info/products/all-streets&quot;&gt;All Streets&lt;/a&gt; left quite the impression on me back in 2007 and it&#39;s stayed in the back of my head ever since. About a year ago I started to wonder if I could pull off something similar with tools I am comfortable with, namely JavaScript for data processing and SVG for drawing things.&lt;/p&gt;
&lt;p&gt;And this happened:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://danburzo.ro/img/every-street/streets.jpg&quot; alt=&quot;Every Street&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here&#39;s a step-by-step account of how I got there.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Get the data&lt;/h2&gt;
&lt;p&gt;Geofabrik thoughtfully packages &lt;a href=&quot;http://download.geofabrik.de/europe.html&quot;&gt;OpenStreetMap data&lt;/a&gt; for every country, so I grabbed the &lt;code&gt;.osm.pbf&lt;/code&gt; for Romania. &lt;a href=&quot;http://wiki.openstreetmap.org/wiki/PBF_Format&quot;&gt;PBF&lt;/a&gt; is an alternative to the XML format in which OSM data is usually kept.&lt;/p&gt;
&lt;p&gt;OSM works with just three data types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;nodes&lt;/strong&gt; define points in space (through latitude &amp;amp; longitude);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ways&lt;/strong&gt; are collections of &lt;em&gt;nodes&lt;/em&gt; which define linear features (yay, streets!) and area boundaries;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;relations&lt;/strong&gt; are sometimes used to explain how other elements work together — e.g. multiple ways that define a longer route.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For our modest purposes, we only need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ways that are labeled as &lt;em&gt;streets&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;The nodes that comprise those ways.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2. Extracting street data from the PBF&lt;/h2&gt;
&lt;p&gt;Time to brush off our Node.js skills and extract the data from the PBF file.&lt;/p&gt;
&lt;p&gt;After a naïve attempt at loading everything in memory, it became apparent that data at this volume needs &lt;a href=&quot;https://github.com/substack/stream-handbook&quot;&gt;streaming&lt;/a&gt; — a technique in which we read data item-by-item, with only fraction in memory at any given time. &lt;a href=&quot;https://github.com/substack/osm-pbf-parser&quot;&gt;&lt;code&gt;osm-pbf-parser&lt;/code&gt;&lt;/a&gt; is a streaming parser which goes through the PBF data and outputs small sets of JSON objects in the formats below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a node...&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;122321&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;lat&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;53.527972600000005&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;lon&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10.0241143&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;...and this is a way&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;way&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;created_by&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Potlatch 0.8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;highway&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;living_street&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Kitzbühler Straße&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;postal_code&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;01217&#39;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;442752&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;231712390&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;442754&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Traversing the PBF file, we can do these checks to pick up the items we need:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isStreet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;way&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;highway&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll put our extracted nodes and streets into plain-text files, with one item per line — this is a format that&#39;s amenable to streaming so it will be easy to read them back on subsequent steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;extract-nodes.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; osm_parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;osm-pbf-parser&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; JSONStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;JSONStream&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;data/data.osm.pbf&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/nodes.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;serializeNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lat &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lon&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Extracting nodes from data file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;osm_parser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;items&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; nodes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serializeNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Finished extracting nodes onto file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;extract-streets.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; osm_parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;osm-pbf-parser&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; JSONStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;JSONStream&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;data/data.osm.pbf&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isStreet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;way&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;highway&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;serializeStreet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;refs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Extracting streets from data file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;osm_parser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;items&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; streets &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isStreet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serializeStreet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Finished extracting streets onto file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which outputs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;nodes.txt&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;360714, 44.493699500000005, 26.0854494
360853, 44.467436600000006, 26.0771428
537912, 44.425765000000006, 26.123137900000003
546140, 44.47436450000001, 26.123994300000003
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;id,latitude,longitude&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;streets.txt&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;656951, 2260664460, 3227352565, 656952, ...
256700851, 2152136723, 659642, 256705252, 2152144026, ...
304797001, 2382014755, 310215524, 255848765, ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;node1,node2,node3,...&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; We&#39;re using &lt;a href=&quot;https://github.com/rvagg/through2&quot;&gt;&lt;code&gt;through2.obj()&lt;/code&gt;&lt;/a&gt; to simplify the pipework.&lt;/p&gt;
&lt;h2&gt;3. Mapping node IDs to their coordinates&lt;/h2&gt;
&lt;p&gt;We now have a huge set of node coordinates and another huge set of node IDs. In order to map the IDs to the coordinates, we need to do two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load the nodes into some sort of database&lt;/li&gt;
&lt;li&gt;Query the database to look up the coordinates for a given ID&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For storage I&#39;ve turned to &lt;a href=&quot;http://leveldb.org/&quot;&gt;LevelDB&lt;/a&gt; which is a pretty straightforward, file-based database. You use it in Node through &lt;a href=&quot;https://github.com/Level/leveldown&quot;&gt;leveldown&lt;/a&gt; and &lt;a href=&quot;https://github.com/Level/levelup&quot;&gt;levelup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;load-nodes.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; split2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;split2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; levelup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;level&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;everystreet&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/nodes.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;creating levelDB database &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;levelup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; write_stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;line&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token literal-property property&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

				&lt;span class=&quot;token comment&quot;&gt;// Prevent memory leak&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;// See: https://github.com/rvagg/node-levelup/issues/298&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token function&quot;&gt;setImmediate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
					i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;write_stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&#39;Finished importing nodes into the database &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a LevelDB database with the name &lt;code&gt;everystreet&lt;/code&gt; (which in turn creates an &lt;code&gt;everystreet&lt;/code&gt; folder where the data is kept), and adds all nodes with &lt;code&gt;key=ID&lt;/code&gt; and &lt;code&gt;value=lat,lon&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; While attempting this I &lt;a href=&quot;https://github.com/rvagg/node-levelup/issues/298&quot;&gt;ran into some memory troubles&lt;/a&gt; to which the easy solution is to delay every 1000th &lt;code&gt;next()&lt;/code&gt; call. There&#39;s also &lt;a href=&quot;https://github.com/maxogden/level-bulk-load&quot;&gt;&lt;code&gt;level-bulk-load&lt;/code&gt;&lt;/a&gt; which attempts to optimize bulk writing in LevelDB, so that might be something to look into.&lt;/p&gt;
&lt;p&gt;Next, let&#39;s map the node IDs to their coordinates in our street definitions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;apply-nodes.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; split2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;split2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; levelup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;level&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; async &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;async&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;everystreet&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets-with-coordinates.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&#39;Applying node data from database &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
		&lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&#39; to street data from file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
		&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;levelup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATABASE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; write_stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;line&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				async&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapSeries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
					line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;node_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
						db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; coords&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
							&lt;span class=&quot;token function&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; coords&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
						&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
						&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
						&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;write_stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&#39;Finished applying node data into file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;re streaming through each street in the data file, querying the database for the coordonates — using &lt;a href=&quot;http://promise-nuggets.github.io/articles/15-map-in-series.html&quot;&gt;&lt;code&gt;async.mapSeries&lt;/code&gt;&lt;/a&gt; to make sure we get back the node data in the correct order — and serializing them into a plain-text file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;streets-with-coordinates.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;44.469672200000005, 26.093109000000002, 44.469469600000004, 26.093366600000003, ...
44.46975080000001, 26.092981700000003, 44.4696756, 26.092841000000004, ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point we&#39;re done with extracting all the data we need but we still need to convert it from geographical coordinates to screen coordinates. &lt;em&gt;*Takes deep breath*&lt;/em&gt;. Onwards!&lt;/p&gt;
&lt;h2&gt;4. Mapping geographical coordintes to screen coordinates&lt;/h2&gt;
&lt;p&gt;There are many &lt;a href=&quot;http://bl.ocks.org/mbostock/3711652&quot;&gt;different ways to project the Earth&#39;s surface&lt;/a&gt; onto 2D space. Many maps are laid out based on the &lt;a href=&quot;http://wiki.openstreetmap.org/wiki/Mercator&quot;&gt;spherical Mercator projection&lt;/a&gt;. Assuming &lt;code&gt;λ&lt;/code&gt; is the longitude and &lt;code&gt;φ&lt;/code&gt; is the latitude, both expressed in radians, the formula is simple:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mercator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;λ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; φ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;λ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; φ &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we dive into it, let&#39;s see what we need to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the &lt;em&gt;bounding box&lt;/em&gt; of all our coordinates and its aspect ratio;&lt;/li&gt;
&lt;li&gt;Transform the geographical coordinates into screen coordinates, based on the bounding box.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;em&gt;bounding box&lt;/em&gt; of our map is the smallest rectangle that contains all the nodes. We can find it by identifying the minimum/maximum longitude and latitude in our dataset. If we transform the points that define the bounding box using the Mercator projection, we can also obtain our final map&#39;s &lt;em&gt;aspect ratio&lt;/em&gt;, which we&#39;re going to use later. The script below computes both:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bbox.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; split2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;split2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets-with-coordinates.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/bbox.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// initial values&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; bbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;north&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// minimum latitude&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;south&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// maximum latitude&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;east&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// minimum longitude&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;west&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// maximum longitude&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toRadians&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deg &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mercator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;λ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; φ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;λ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; φ &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;lat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mercator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRadians&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lon&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toRadians&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Finding bounding box in file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;line&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; coords &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; coords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; lat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseFloat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coords&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;j&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					lon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseFloat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coords&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;j &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lat &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;north&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;north &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lat &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;south&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;south &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lon &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;east&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;east &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lon&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lon &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;west&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;west &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lon&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; nw_projected &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;north&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;west&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; se_projected &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;south&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;east&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; west &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nw_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; north &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nw_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; east &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; se_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; south &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; se_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;bbox&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bbox&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;east &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; west&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;north &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; south&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script outputs the following information:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bbox.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;bbox&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;north&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48.4394855&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;south&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;43.578847700000004&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;east&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;29.726612400000004&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;west&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20.198656500000002&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;ratio&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.3601773494902782&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s now take our geographical coordinates transform them using the Mercator projection; afterwards, we express them as percentages within the map&#39;s bounding box which will make it easy for us to draw at any scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;map-coordinates.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; split2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;split2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets-with-coordinates.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets-with-coordinates-mapped.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BBOX_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/bbox.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// read the bounding box and project it using Mercator&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; o &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BBOX_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; nw_projected &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;north&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;west&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; se_projected &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;south&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;east&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; north &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nw_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; south &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; se_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; west &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nw_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; east &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; se_projected&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toRadians&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deg &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mercator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;λ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; φ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;λ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; φ &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;lat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mercator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRadians&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lon&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toRadians&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;percent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;lonlat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lonlat&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; west&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;east &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; west&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lonlat&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; south&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;north &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; south&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Mapping nodes using Mercador projection from file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;line&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; coords &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; pts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; coords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				pts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
					&lt;span class=&quot;token function&quot;&gt;percent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coords&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; coords&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;token string&quot;&gt;&#39;Finished mapping nodes using Mercator projection onto file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
				&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All nodes should be now in the &lt;code&gt;[0,1]&lt;/code&gt; range:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;streets-with-coordinates-mapped.txt&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.6186481719547, 0.17686588159630384; 0.6186752081839509, 0.17682535252522288; ...
0.618634811271534, 0.1768816051533656; 0.6186200442006669, 0.17686656174973386; ...
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. Drawing the map in SVG&lt;/h2&gt;
&lt;p&gt;We now have everything we need to start drawing some SVG paths. This is the structure we&#39;re aiming for:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;svg&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;viewbox&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;path&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;M x1 y1 L x2 y2 L x3 y3 ...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
	...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember the &lt;em&gt;map ratio&lt;/em&gt; we computed earlier? We can use that to derive the height of our map based on a width of our choice.
And to transform our points from the &lt;code&gt;[0,1]&lt;/code&gt; range to SVG-ready coordinates, we just need to factor in the map&#39;s dimensions:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; map_width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; map_height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; map_width &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; map_ratio&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; screen_x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; longitude &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; map_width&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; screen_y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; latitude&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; map_height&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; The &lt;code&gt;1 - latitude&lt;/code&gt; is to account for the fact that the origin of SVG coordinates is at the &lt;em&gt;top left&lt;/em&gt; corner while our coordinates assume an origin in the &lt;em&gt;bottom left&lt;/em&gt; corner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;generate-svg.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; through2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;through2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; split2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;split2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; multiline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;multiline&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_MAP_WIDTH&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// px&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets-with-coordinates-mapped.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/streets.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BBOX_FILE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output/bbox.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; o &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BBOX_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; map_width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_MAP_WIDTH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; map_height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; map_width &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ratio&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; write_stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

write_stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;{w}&#39; height=&#39;{h}&#39; viewbox=&#39;0 0 {w} {h}&#39;&gt;&quot;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;{w&#92;}&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; map_width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;{h&#92;}&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; map_height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Generating SVG from file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		through2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;line&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; path_data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
					&lt;span class=&quot;token string&quot;&gt;&#39;M &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
					line
						&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
						&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;pt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
							&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; lonlat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
							&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
								lonlat&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; map_width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
								&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lonlat&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; map_height
							&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
						&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
						&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; L &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
					&lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;path d=&quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
						path_data &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
						&lt;span class=&quot;token string&quot;&gt;&#39;&quot; stroke-width=&quot;0.1&quot; stroke=&quot;black&quot; fill=&quot;none&quot;/&gt;&#92;n&#39;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;/svg&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;write_stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Finished generating SVG onto file: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voilá! We have our all streets in Romania drawn up in SVG, which you can open in your browser (&lt;a href=&quot;http://benfry.com/writing/archives/62&quot;&gt;as opposed to other tools&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Here it is in all its glory: &lt;a href=&quot;https://danburzo.ro/img/every-street/streets.svg&quot;&gt;streets.svg&lt;/a&gt; &lt;em&gt;(Warning: 262MB file!)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Making it printable&lt;/strong&gt;. Loading or converting a 262MB SVG is no easy feat, but ImageMagick somehow miraculously created a wall-sized PNG image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;convert -density 900 output/streets.svg output/streets.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another idea worth pursuing is making &lt;a href=&quot;https://github.com/atom/electron&quot;&gt;electron&lt;/a&gt; print out a PDF.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimizing the map&lt;/strong&gt;. Taking into account the output resolution, one could simplify the paths with &lt;a href=&quot;http://mourner.github.io/simplify-js/&quot;&gt;simplify.js&lt;/a&gt; to eliminate details without affecting the appearance (e.g. points that are very close together):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;simplify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And considering the browser needs to build the DOM tree along with drawing the millions of paths in our file, one easy fix to have fewer DOM nodes is to batch the path data into multipaths of, say, a thousand paths:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; path_buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  path_buffer &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; path_data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;counter&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;path d=&quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; path_buffer &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&quot; stroke-width=&quot;0.1&quot; stroke=&quot;black&quot; fill=&quot;none&quot;/&gt;&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    path_buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;I hope you&#39;ve enjoyed this short foray into mapping! You can find all the scripts discussed here on Github: &lt;a href=&quot;https://github.com/danburzo/every-street&quot;&gt;danburzo/every-street&lt;/a&gt;. If you have any idea on how to make this workflow better, &lt;a href=&quot;https://github.com/danburzo/every-street/issues/new&quot;&gt;I&#39;d love to hear it&lt;/a&gt;!&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Eight Versions of Satie’s Gnossienne No. 1 (1890)</title>
		<link href="https://danburzo.ro/gnossienne-1/" />
		<updated>2015-12-26T00:00:00Z</updated>
		<id>https://danburzo.ro/gnossienne-1/</id>
		<content type="html"
			>&lt;h3&gt;1. &lt;a href=&quot;https://youtu.be/1FQ4EVMCSV0&quot;&gt;Family Fodder - The Big Dig&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The Big Dig / Plant Life (1982)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2. &lt;a href=&quot;https://youtu.be/R0ACJfsUHnw&quot;&gt;Daisaku Kume - Azuma No Theme&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From the sountrack of Takeshi Kitano&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Violent_Cop_(1989_film)&quot;&gt;&lt;em&gt;Violent Cop&lt;/em&gt;&lt;/a&gt; (1989).&lt;/p&gt;
&lt;h3&gt;3. &lt;a href=&quot;https://www.youtube.com/watch?v=aUcb0B5ibtY&quot;&gt;Jun Miyake - Gnossienne #1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(Glam Exotica!, 1999)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;4. &lt;a href=&quot;https://youtu.be/fQpUTUu0d34&quot;&gt;Arthur H &amp;amp; Feist - La chanson de Satie&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(Adieu tristesse, 2005)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;5. &lt;a href=&quot;https://www.youtube.com/watch?v=Hwyr6-X2OmY&quot;&gt;Beltuner - Gnossienne #1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(Beltuner, 2005)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;6. &lt;a href=&quot;https://youtu.be/bHY2ldYdaKA&quot;&gt;Chicha Libre - Gnossienne No. 1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(¡Sonido Amazonico!, 2008)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;7. &lt;a href=&quot;https://youtu.be/1d6-4Y9GnsE&quot;&gt;Erkan Oğur - Gnossienne No. 1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(Mommo OST, 2009)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;8. &lt;a href=&quot;https://youtu.be/NlofCrcQIUw&quot;&gt;Orange Blossom - Ya Sîdî&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(Under the Shade of Violets, 2014)&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://danburzo.ro/linked-erik-satie/&quot;&gt;More Satie&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2015</title>
		<link href="https://danburzo.ro/favorite-records-2015/" />
		<updated>2015-12-18T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2015/</id>
		<content type="html"
			>&lt;p&gt;Great albums released this year, A-Z with &lt;strong&gt;favorites are in bold&lt;/strong&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Album&lt;/th&gt;
&lt;th&gt;♫&lt;/th&gt;
&lt;th&gt;🍏&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adam Bryanbaum Wiltzie – Travels in Constants Vol. 24&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://adambryanbaumwiltzie.bandcamp.com/album/travels-in-constants-vol-24&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/nipN7&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthony Child – Electronic Recordings from Maui Jungle Vol. 1&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://editionsmego.bandcamp.com/album/electronic-recordings-from-maui-jungle-vol-1&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/xx__9&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arca – Mutant&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/arca1000000/soichiro&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/MmDE-&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bang on a Can All-Stars – Field Recordings&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://bangonacan.bandcamp.com/album/field-recordings&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Pgjc7&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beirut – No No No&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=G8lOkgyPcaU&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/C9DC7&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Björk – Vulnicura&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=gQEyezu7G20&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/yBao5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chilly Gonzales – Chambers&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/chillygonzales/sets/chilly-gonzales-chambers&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/dJTy5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Demdike Stare – Testpressing #007: Rathe/Patchwork&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLpOEGKO1FO70SGUZyGm-u7a4fXluvnJwm&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/yo5g8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Father John Misty – I Love You, Honeybear&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=caMfvhKIgBo&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/PVtO3&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Four Tet – Morning/Evening&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fourtet.bandcamp.com/album/morning-evening&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/6-It8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Godspeed You! Black Emperor – &#39;Asunder, Sweet and Other Distress&#39;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://cstrecords.com/cst111/&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/zH7L5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grimes – Art Angels&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Tv9YoYCKNoE&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/bzvP-&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Holly Herndon – Platform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://hollyherndon.bandcamp.com/album/platform&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Zjz95&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventions – Maze of Woods&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://inventions.bandcamp.com/album/maze-of-woods&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/R7Nq5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jamie xx – In Colour&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=0fOHh5Q7Q1E&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/w6ju6&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jeff Bridges – Sleeping Tapes&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.dreamingwithjeff.com/&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Lkj_9&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenny Hval – Apocalypse, girl&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://jennyhval.bandcamp.com/album/apocalypse-girl&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/0ZeW5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jim O&#39;Rourke – Simple Songs&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.dragcity.com/products/simple-songs&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Joanna Newsom – Divers&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=48xlgXqQKLA&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Jon Hopkins – Late Night Tales&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://latenighttales.bandcamp.com/album/late-night-tales-jon-hopkins&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Z1Mq9&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kurt Vile – b’lieve i’m goin down...&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=659pppwniXA&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/ZIgE8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lubomyr Melnyk – Rivers and Streams&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/erasedtapes/lubomyr-melnyk-parasol-excerpt&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Luke Abbott – Music For A Flat Landscape: Original Soundtrack to &#39;The Goob&#39;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://lukeabbottmusic.bandcamp.com/album/music-for-a-flat-landscape-original-soundtrack-to-the-goob&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Ozn36&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;METZ – II&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://metz.bandcamp.com/album/ii&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/UG4s5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max Richter – From Sleep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8dvpT0hA0Lk&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/TNux8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nava Mamă – Nava Mamă&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://navamama.bandcamp.com/album/nava-mam&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nils Frahm – Late Night Tales&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://latenighttales.bandcamp.com/album/late-night-tales-nils-frahm&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/IH7V9&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nils Frahm – Solo&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.pianoday.org/&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Zqkr6&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noveller – Fantastic Planet&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://novellermusic.bandcamp.com/album/fantastic-planet&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oneohtrix Point Never – Garden of Delete&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://youtu.be/td-e4i2BL_Q&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/w-JU9&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OZmotic &amp;amp; Fennesz – AirEffect&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BkB9z2G67Kg&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/43948&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protomartyr – The Agent Intellect&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://protomartyr.bandcamp.com/album/the-agent-intellect&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/qI9u8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rachel Grimes – The Clearing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://rachelgrimes.bandcamp.com/album/the-clearing&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/u8T25&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Richard Hawley – Hollow Meadows&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.theguardian.com/music/musicblog/2015/sep/03/richard-hawley-hollow-meadows-exclusive-album-stream&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/0vvw8&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Richie Hawtin –  From My Mind To Yours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://plus8records.bandcamp.com/album/from-my-mind-to-yours&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/HXxk_&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sleaford Mods – Key Markets&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/sleafordmods/face-to-faces&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/TU4y7&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sleater-Kinney – No Cities to Love&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://sleaterkinney.bandcamp.com/album/no-cities-to-love&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/yk4q3&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Squarepusher – Damogen Furies&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-usL_kRXm6s&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/as7P5&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;St Germain – St Germain&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/nonesuchrecords/st-germain-sittin-here-edit&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/Azn66&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sufjan Stevens – Carrie and Lowell&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://music.sufjan.com/album/carrie-lowell&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/igo94&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sunn O))) – Kannon&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://sunn.bandcamp.com/album/kannon&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/8og9-&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tame Impala – Currents&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://youtu.be/hefh9dFnChY&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/uWw76&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Gurdjieff Ensemble, Levon Eskenian – Komitas&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.ecmrecords.com/catalogue/1443525465&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tigran Hamasyan – Luys i Luso&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.ecmrecords.com/catalogue/1441200392&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viet Cong – Viet Cong&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://vietcong.bandcamp.com/album/viet-cong&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/e94r3&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wil Bolton – Marram&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://wilbolton.bandcamp.com/album/marram&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;William Basinski – The Deluge/Cascade&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://williambasinski.bandcamp.com/album/the-deluge&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/5oZx6&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wolfgang Voigt – Rückverzauberung 10 / Nationalpark&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.kompakt.fm/releases/rueckverzauberung_10_nationalpark&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/z9Po7&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yuta Nagashima – White Sleep&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://darlamusic.bandcamp.com/album/white-sleep&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://itun.es/ro/vpDO4&quot;&gt;🍏&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A few other year-end lists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.roughtrade.com/aoty2015&quot;&gt;Rough Trade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://thequietus.com/articles/19350-best-albums-2015&quot;&gt;The Quietus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.factmag.com/2015/12/09/the-50-best-albums-of-2015/&quot;&gt;FACT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://the-attic.net/features/1616/the-attic:-favorite-albums-of-2015.html&quot;&gt;The Attic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;+ Largehearted Boy&#39;s &lt;a href=&quot;http://www.largeheartedboy.com/blog/archive/2015/11/essential_and_i_1.html&quot;&gt;excellent meta-list&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Walking in Walden</title>
		<link href="https://danburzo.ro/walden-walk/" />
		<updated>2015-01-20T00:00:00Z</updated>
		<id>https://danburzo.ro/walden-walk/</id>
		<content type="html"
			>&lt;p&gt;Every sentence in &lt;a href=&quot;http://en.wikipedia.org/wiki/Walden&quot;&gt;&lt;em&gt;Walden&lt;/em&gt;&lt;/a&gt; that contains the words &lt;em&gt;I&lt;/em&gt; and &lt;em&gt;walk&lt;/em&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&amp;quot;where I see in my daily walks human beings living in sties, and all winter with an open door, for the sake of light, without any visible, often imaginable, wood-pile, and the forms of both old and young are permanently contracted by the long habit of shrinking from cold and misery, and the development of all their limbs and faculties is checked.&lt;/p&gt;
&lt;p&gt;&amp;quot;I walked about the outside, at first unobserved from within, the window was so deep and high.&lt;/p&gt;
&lt;p&gt;&amp;quot;I never in all my walks came across a man engaged in so simple and natural an occupation as building his house.&lt;/p&gt;
&lt;p&gt;&amp;quot;It would surpass the powers of a well man nowadays to take up his bed and walk, and I should certainly advise a sick one to lay down his bed and run.&lt;/p&gt;
&lt;p&gt;&amp;quot;I walked over each farmer&#39;s premises, tasted his wild apples, discoursed on husbandry with him, took his farm at his price, at any price, mortgaging it to him in my mind.&lt;/p&gt;
&lt;p&gt;&amp;quot;As I walk along the stony shore of the pond in my shirt-sleeves, though it is cool as well as cloudy and windy, and I see nothing special to attract me, all the elements are unusually congenial to me.&lt;/p&gt;
&lt;p&gt;&amp;quot;There was a certain positive originality, however slight, to be detected in him, and I occasionally observed that he was thinking for himself and expressing his own opinion, a phenomenon so rare that I would any day walk ten miles to observe it, and it amounted to the re-origination of many of the institutions of society.&lt;/p&gt;
&lt;p&gt;&amp;quot;As I walked in the woods to see the birds and squirrels, so I walked in the village to see the men and boys.&lt;/p&gt;
&lt;p&gt;&amp;quot;Sometimes, after coming home thus late in a dark and muggy night, when my feet felt the path which my eyes could not see, dreaming and absent-minded all the way, until I was aroused by having to raise my hand to lift the latch, I have not been able to recall a single step of my walk, and I have thought that perhaps my body would find its way home if its master should forsake it, as the hand finds its way to the mouth without assistance.&lt;/p&gt;
&lt;p&gt;&amp;quot;As I walked on the railroad causeway, I used to wonder at the halo of light around my shadow, and would fain fancy myself one of the elect.&lt;/p&gt;
&lt;p&gt;&amp;quot;Once I was surprised to see a cat walking along the stony shore of the pond, for they rarely wander so far from home.&lt;/p&gt;
&lt;p&gt;&amp;quot;I sometimes left a good fire when I went to take a walk in a winter afternoon.&lt;/p&gt;
&lt;p&gt;&amp;quot;For many weeks I met no one in my walks but those who came occasionally to cut wood and sled it to the village.&lt;/p&gt;
&lt;p&gt;&amp;quot;It chanced that I walked that way across the fields the following night, about the same hour, and hearing a low moaning at this spot, I drew near in the dark, and discovered the only survivor of the family that I know, the heir of both its virtues and its vices, who alone was interested in this burning, lying on his stomach and looking over the cellar wall at the still smouldering cinders beneath, muttering to himself, as is his wont. &amp;quot; I felt it, and still remark it almost daily in my walks, for by it hangs the history of a family.&lt;/p&gt;
&lt;p&gt;&amp;quot;But no weather interfered fatally with my walks, or rather my going abroad, for I frequently tramped eight or ten miles through the deepest snow to keep an appointment with a beech tree, or a yellow birch, or an old acquaintance among the pines.&lt;/p&gt;
&lt;p&gt;&amp;quot;As I walked over the long causeway made for the railroad through the meadows, I encountered many a blustering and nipping wind, for nowhere has it freer play.&lt;/p&gt;
&lt;p&gt;&amp;quot;Sometimes, notwithstanding the snow, when I returned from my walk at evening I crossed the deep tracks of a woodchopper leading from my door, and found his pile of whittlings on the hearth, and my house filled with the odor of his pipe.&lt;/p&gt;
&lt;p&gt;&amp;quot;Walden, being like the rest usually bare of snow, or with only shallow and interrupted drifts on it, was my yard where I could walk freely when the snow was nearly two feet deep on a level elsewhere and the villagers were confined to their streets.&lt;/p&gt;
&lt;p&gt;&amp;quot;and then suddenly pausing with a ludicrous expression and a gratuitous somerset, as if all the eyes in the universe were eyed on him—for all the motions of a squirrel, even in the most solitary recesses of the forest, imply spectators as much as those of a dancing girl—wasting more time in delay and circumspection than would have sufficed to walk the whole distance—I never saw one walk—and then suddenly, before you could say Jack Robinson, he would be in the top of a young pitch pine, winding up his clock and chiding all imaginary spectators, soliloquizing and talking to all the universe at the same time—for no reason that I could ever detect, or he himself was aware of, I suspect.&lt;/p&gt;
&lt;p&gt;&amp;quot;The ice in the pond at length begins to be honeycombed, and I can set my heel in it as I walk.&lt;/p&gt;
&lt;p&gt;&amp;quot;I delight to come to my bearings—not walk in procession with pomp and parade, in a conspicuous place, but to walk even with the Builder of the universe, if I may—not to live in this restless, nervous, bustling, trivial Nineteenth Century, but stand or sit thoughtfully while it goes by.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I extracted the text by running this on the &lt;a href=&quot;http://www.gutenberg.org/files/205/205-h/205-h.htm&quot;&gt;HTML version from Project Gutenberg&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;p&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;paragraph&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; paragraph
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[&#92;.&#92;?&#92;!&#92;;]&#92;s*&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;sentence&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sentence&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;walk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; 
					&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; sentence&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;I &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;sentences&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sentences&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;sentences&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sentences
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;. &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;n&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;s+&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#92;n&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Further reading:&lt;/h3&gt;
&lt;p&gt;Darius Kazemi&#39;s note on &lt;a href=&quot;http://tinysubversions.com/notes/aphorism-detection/&quot;&gt;aphorism detection&lt;/a&gt;, especially this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Let&#39;s start out by being incredibly naive about aphorisms. I always like to start with the easiest most naive solution possible, because sometimes you get lucky and the easy solution is Good Enough and you can implement it and go do something else with your life.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Poet Robert Fitterman went about doing &lt;a href=&quot;http://www.poets.org/poetsorg/poem/hemingway-reader-sun-also-also-rises&quot;&gt;something similar by hand&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When I was 13, my brother gave me a copy of Hemingway’s &lt;em&gt;The Sun Also Rises&lt;/em&gt;. It was my first foray into real Literature and I hated it. Even with little or no way to enter the novel, I dutifully slugged through it (I mean, what is cog-nak anyway?) Years later, I have returned to revisit the relationship. In this version, I have erased my way through Hemingway’s original text, leaving behind only the phrases that begin with the pronoun ‘I’.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And Thoreau did write an essay &lt;a href=&quot;http://www.gutenberg.org/files/1022/1022-h/1022-h.htm&quot;&gt;on walking&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>My favorite records from 2014</title>
		<link href="https://danburzo.ro/favorite-records-2014/" />
		<updated>2015-01-13T00:00:00Z</updated>
		<id>https://danburzo.ro/favorite-records-2014/</id>
		<content type="html"
			>&lt;p&gt;In no particular order:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Album&lt;/th&gt;
&lt;th&gt;♫&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mogwai – Rave Tapes&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.subpop.com/releases/mogwai/rave_tapes&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A Winged Victory for the Sullen – Atomos&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/awvfts/atomos-vi-edit&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scott Walker + Sunn O))) – Soused&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.4ad.com/videos/387&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sharon Van Etten – Are We There&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/jagjaguwar/sharon-van-etten-every-time-the-sun-comes-up&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;This Will Destroy You – Another Language&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://thiswilldestroyyou.bandcamp.com/album/another-language&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fennesz – Bécs&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/forcedexposurepublicity/01-static-kings&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flying Lotus – You&#39;re Dead!&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/flyinglotus/coronus-the-terminator&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caribou – Our Love&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/caribouband/cant-do-without-you&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blonde Redhead – Barragán&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://blonde-redhead.com/barragan/&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aphex Twin – Syro&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/warp-records/aphex-twin-minipops-67-1202source-field-mix&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timber Timbre – Hot Dreams&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/artsandcrafts/hot-dreams-1&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The War on Drugs – Lost in the Dream&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/secretlycanadian/the-war-on-drugs-red-eyes&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plaid – Reachy Prints&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/plaid/hawkmoth&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amen Dunes – Love&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/sacredbones/amen-dunes-lonely-richard&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strand of Oaks – HEAL&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/deadoceans/strand-of-oaks-goshen-97-1&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grouper – Ruins&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/kranky/grouper-call-across-rooms&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jon Hopkins – Asleep Versions&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=QECYg_6rM58&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ben Frost – A U R O R A&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://benfrost.bandcamp.com/album/a-u-r-o-r-a&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jóhann Jóhannsson – McCanick OST&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/milanrecords/j-hann-j-hannsson-payphone&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leyland Kirby – We drink to forget the coming storm&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vivaldi Recomposed by Max Richter (reissue)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/max-richter/sets/recomposed-by-max-richter&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steve Reich – Radio Rewrite&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.nonesuch.com/albums/radio-rewrite&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventions – Inventions&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/temporary-residence-ltd-1/inventions-entity&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lisa Gerrard – Twilight Kingdom&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://lisagerrard.com/store/twilightkingdom.html&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locust – After the Rain&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/editionsmego/locust-ill-be-there-emego-205&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kyle Bobby Dunn – Kyle Bobby Dunn and the Infinite Sadness&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/studentsofdecay/sets/kyle-bobby-dunn-infinite-sadness&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Various Artists – Pop Ambient 2015&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://www.kompakt.fm/releases/pop_ambient_2015&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gazelle Twin – UNFLESH&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://gazelletwin.bandcamp.com/album/unflesh&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rhyton – Kykeon&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/thrilljockey/rhyton-topkapi&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andy Stott – Faith In Strangers&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/modernlove/andy-stott-faith-in-strangers&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Neel – Phobos&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/spectrumspools/life-on-laputa-regio-by-neel&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lubomyr Melnyk – Evertina&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/erasedtapes/lubomyr-melnyk-evertina&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tujiko Noriko – My Ghost Comes Back&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/editionsmego/05-through-the-rain&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hildur Guðnadóttir – Saman&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://touch333.bandcamp.com/album/saman&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mike Weis – Don&#39;t Know, Just Walk&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://zelienople.bandcamp.com/album/dont-know-just-walk-2&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Philip Corner – Satie Slowly&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://unseenworlds.bandcamp.com/album/satie-slowly&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Klara Lewis – Ett&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://soundcloud.com/editionsmego/klara-lewis-shine-emego-190&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wovenhand – Refractory Obdurate&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://deathwishinc.bandcamp.com/album/refractory-obdurate&quot;&gt;♫&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;+ a couple of year-end lists to catch up with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://thequietus.com/articles/16739-albums-of-the-year-2014&quot;&gt;The Quietus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.roughtrade.com/aoty2014&quot;&gt;Rough Trade&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Timeline of favorite records: &lt;a href=&quot;https://danburzo.ro/favorite-records-2014/&quot;&gt;2014&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2015/&quot;&gt;2015&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2016/&quot;&gt;2016&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2017/&quot;&gt;2017&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2018/&quot;&gt;2018&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2019/&quot;&gt;2019&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2020/&quot;&gt;2020&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2021/&quot;&gt;2021&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2022/&quot;&gt;2022&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2023/&quot;&gt;2023&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2024/&quot;&gt;2024&lt;/a&gt;, &lt;a href=&quot;https://danburzo.ro/favorite-records-2025/&quot;&gt;2025&lt;/a&gt;.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Recently: Oldies</title>
		<link href="https://danburzo.ro/recently-ii/" />
		<updated>2014-11-07T00:00:00Z</updated>
		<id>https://danburzo.ro/recently-ii/</id>
		<content type="html"
			>&lt;h3&gt;Reading&lt;/h3&gt;
&lt;p&gt;Simon Reynold&#39;s &lt;em&gt;&lt;a href=&quot;http://retromaniabysimonreynolds.blogspot.com/&quot;&gt;Retromania: Pop Culture&#39;s Addiction To Its Own Past&lt;/a&gt;&lt;/em&gt; explores the recent phenomenon of near-past nostalgia.&lt;/p&gt;
&lt;h3&gt;Listening&lt;/h3&gt;
&lt;p&gt;I got the &lt;em&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/The_Beatles_in_Mono&quot;&gt;Beatles in Mono&lt;/a&gt;&lt;/em&gt; version of &lt;em&gt;Revolver&lt;/em&gt; the other day, in part because it was there in the bookstore but also because I love &lt;em&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7xL1ffMlzKY&quot;&gt;Tomorrow Never Knows&lt;/a&gt;&lt;/em&gt;, which you might remember from, you know, &lt;a href=&quot;https://www.youtube.com/watch?v=p5NX1FC-7-w&quot;&gt;every Chemical Brothers song&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=s5FyfQDO5g0&quot;&gt;ever&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A note inside the sleeve reads:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This album was cut for vinyl from the original master tapes by using a completely analogue signal path and with constant reference to the notes made by the cutting engineer for the first pressing of the LP. It has been made with current technology and without imposing the restrictions necessitated by the limitations of record players in the 1960s. Consequently, this version reveals more of the content of the audio on the master tapes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&#39;s just an incredible album. Oh and the fact that it&#39;s universally recognized as one of the essential records of the 20th century makes its &lt;a href=&quot;http://en.wikipedia.org/wiki/Revolver_%28Beatles_album%29&quot;&gt;track-by-track, meticulously researched Wikipedia page&lt;/a&gt; an exquisite read.&lt;/p&gt;
&lt;hr /&gt;
&lt;img src=&quot;https://danburzo.ro/img/sons-of-the-wind.jpg&quot; width=&quot;400&quot; /&gt;
&lt;p&gt;On the same trip I got &lt;em&gt;&lt;a href=&quot;http://soundwalkcollective.com/index.php?/sons-of-the-wind/&quot;&gt;Sons of the Wind&lt;/a&gt;&lt;/em&gt;, &amp;quot;a sound journey along the Danube [featuring] recordings from Gypsy villages in Ukraine, Moldavia, Romania, Bulgaria, Macedonia, Serbia, Hungary, Slovakia, Austria&amp;quot;. Its haunting cover (shown above) stopped me in my tracks and ultimately convinced me to give it a listen despite not knowing what&#39;s inside. I was expecting the usual Gypsy music compilation but instead I was greeted by a sound poem mixing field recording, ambient music, spoken word, acapella vocals and traditional instruments. This is design at its best: turning my attention to something I would have otherwise ignored, and then blowing my socks off in exchange of my trust.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://vimeo.com/73966560&quot;&gt;This video&lt;/a&gt; by Soundwalk Collective gives you an idea.&lt;/p&gt;
&lt;p&gt;There&#39;s also a &lt;a href=&quot;http://www.virginieluc.com/ecouter/fils-du-vent/&quot;&gt;three-part radio essay&lt;/a&gt; by Virginie Luc, who wrote the texts for the project.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Linked: Erik Satie</title>
		<link href="https://danburzo.ro/linked-erik-satie/" />
		<updated>2014-10-24T00:00:00Z</updated>
		<id>https://danburzo.ro/linked-erik-satie/</id>
		<content type="html"
			>&lt;p class=&quot;preamble&quot;&gt;&lt;em&gt;Linked&lt;/em&gt; is where I rummage through my notes and bookmarks for bits and bobs around a subject.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;My only nourishment consists of food that is white: eggs, sugar, shredded bones, the fat of dead animals, veal, salt, coco-nuts, chicken cooked in white water, mouldy fruit, rice, turnips, sausages in camphor, pastry, cheese (white varieties), cotton salad, and certain kinds of fish (without their skin). I boil my wine and drink it cold mixed with the juice of the Fuschia. I have a good appetite but never talk when eating for fear of strangling myself.&amp;quot;&lt;/p&gt;
&lt;p&gt;— Erik Satie, &lt;em&gt;A Day in the Life of a Musician&lt;/em&gt; (via &lt;a href=&quot;http://www.ubu.com/papers/satie_day.html&quot;&gt;UbuWeb&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Erik Satie was fond of invented musical taxonomy: &lt;em&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Gnossiennes&quot;&gt;gnossiennes&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Gymnop%C3%A9dies&quot;&gt;gymnopédies&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Vexations&quot;&gt;vexations&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Ogives&quot;&gt;ogives&lt;/a&gt;&lt;/em&gt;. His concept of &lt;em&gt;furniture music&lt;/em&gt; was essential to the development of minimalist and ambient music. And &lt;a href=&quot;http://www.press.umich.edu/8718/elevator_music&quot;&gt;shitty elevator music&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ErtNBlFpxWM&quot;&gt;Branka Parlić&#39;s unhurried pace&lt;/a&gt; — what I imagine Satie meant when he instructed the performer to play the pieces &lt;em&gt;lent et douloureux&lt;/em&gt; (slowly, sadly, gravely) — has pretty much ruined other renditions for me: they come off as sloppy and hasty in comparison. I&#39;ve probably listened to her album &lt;em&gt;Initiés &#39;88 - Initiés &#39;99&lt;/em&gt; a hundred times. Sadly, it&#39;s been out of print for a while, so no chance to get it in a physical format. Next, listen to &lt;a href=&quot;https://www.youtube.com/watch?v=_hMw1C6fPt8&quot;&gt;her play piano works by Philip Glass&lt;/a&gt; to convince yourself you should &lt;a href=&quot;https://shop.klicktrack.com/438117&quot;&gt;buy her latest album&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Eisoptrophobia&lt;/em&gt; translates as &amp;quot;fear of, or aversion to, reflections&amp;quot;, the light version of which has possibly been impressed upon me by the seriously unsettling &lt;a href=&quot;https://www.youtube.com/watch?v=fEJBdXUYqwA&quot;&gt;mirror scene in the film &lt;em&gt;Below&lt;/em&gt;&lt;/a&gt; that resulted in this ominous undertone to any mirror gazing ever since. But it&#39;s also the name of &lt;a href=&quot;http://www.akirarabelais.com/vi/o/m/e/album.html&quot;&gt;an album by Akira Rabelais&lt;/a&gt; who crafts distorted, shimmering pieces based on &lt;a href=&quot;http://www.akirarabelais.com/vi/o/m/e/c/08.mp3&quot;&gt;music by Satie&lt;/a&gt;, &lt;a href=&quot;http://www.akirarabelais.com/vi/o/m/e/c/19.mp3&quot;&gt;Bartók&lt;/a&gt; and &lt;a href=&quot;http://www.akirarabelais.com/vi/o/m/e/c/05.mp3&quot;&gt;Carté&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;The smallest work by Satie is small in the way that a keyhole is small. Everything changes when you put your eye to it--or your ear.&amp;quot;&lt;/p&gt;
&lt;p&gt;— Jean Cocteau&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Further down the distorsion spectrum, we&#39;ve got Bionulor a.k.a. Sebastian Banaszczyk&#39;s &lt;em&gt;&lt;a href=&quot;http://bionulor.bandcamp.com/album/erik&quot;&gt;Erik&lt;/a&gt;&lt;/em&gt;, a reworking of Satie&#39;s &lt;em&gt;Gymnopédies&lt;/em&gt; that &lt;a href=&quot;http://thequietus.com/articles/13587-polish-music-album-reviews&quot;&gt;Luke Turner accurately describes&lt;/a&gt; as &amp;quot;the sound of a hangover inside Brian Eno&#39;s splendid dome after he&#39;s had a terrible sherry-fuelled row with William Basinski...&amp;quot;&lt;/p&gt;
&lt;p&gt;Finally, in what could have been an early Christmas, it seems that every contemporary piano player I know about and listen to is featured on the compilation &lt;em&gt;&lt;a href=&quot;http://www.arbouserecordings.com/view-release.php?id=36&quot;&gt;Erik Satie et les nouveaux jeunes&lt;/a&gt;&lt;/em&gt;. We&#39;ve got Max Richter, Rachel Grimes, Sylvain Chauveau, Hauschka, Eluvium, Dustin O&#39;Halloran, Nils Frahm, all the cool kids. The original is out of print, but &lt;a href=&quot;http://arbouserecordings.bandcamp.com/album/erik-satie-et-les-nouveaux-jeunes-version-2&quot;&gt;some sort of reissue is going on&lt;/a&gt;. Fingers crossed!&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>Recently: living in cold places</title>
		<link href="https://danburzo.ro/recently-i/" />
		<updated>2014-10-22T00:00:00Z</updated>
		<id>https://danburzo.ro/recently-i/</id>
		<content type="html"
			>&lt;h3&gt;Reading&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://www.amazon.co.uk/Consolations-Forest-Alone-Cabin-Middle/dp/0141975474&quot;&gt;Consolations of the Forest&lt;/a&gt;: Alone in a Cabin in the Middle Taiga&lt;/em&gt; by Sylvain Tesson, a journaled account of the six months he lived in a small cabin next to lake Baikal in Siberia. I&#39;m reading it in Romanian but the English version and has a &lt;a href=&quot;http://penguindesign.tumblr.com/post/51715097970/consolations-of-the-forest-alone-in-a-cabin-in&quot;&gt;much nicer cover&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thinking about expanses of snow and ice reminded me of &lt;a href=&quot;http://www.themorningnews.org/article/twilight-on-the-tundra&quot;&gt;this essay from The Morning News&lt;/a&gt; about a dog sled race across the tundras of Russia.&lt;/p&gt;
&lt;p&gt;And on a completely different scale, the Lykov family &lt;a href=&quot;http://www.smithsonianmag.com/history/for-40-years-this-russian-family-was-cut-off-from-all-human-contact-unaware-of-world-war-ii-7354256&quot;&gt;spent 40 years in complete isolation&lt;/a&gt; before being discovered in 1978 by a group of geologists. Agafia Lykov, the last surviving member of the family, &lt;a href=&quot;https://www.youtube.com/watch?v=tt2AYafET68&quot;&gt;still lives there&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Watching&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Happy_People:_A_Year_in_the_Taiga&quot;&gt;&lt;em&gt;Happy People: A Year in the Taiga&lt;/em&gt;&lt;/a&gt;, a documentary put together and narrated by Werner Herzog based on footage by Dmitry Vasyukov that I wanted to watch for a while sounded like a good complement to Tesson&#39;s book. (It was.)&lt;/p&gt;
&lt;h3&gt;Listening&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://scott-o.com/&quot;&gt;&lt;em&gt;Soused&lt;/em&gt;&lt;/a&gt;, a collaboration between Scott Walker and Sun O))) sounds like boiling tar, anvils and nightmare fuel. In other words, it&#39;s perfect and I can&#39;t stop listening to it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.erasedtapes.com/store/index/ERATP061&quot;&gt;&lt;em&gt;Atomos&lt;/em&gt;&lt;/a&gt; by A Winged Victory for the Sullen is the much anticipated follow-up to their excellent self-titled debut and it might very well be my favorite record of the year.&lt;/p&gt;
&lt;p&gt;Flying Lotus&#39; latest offering, &lt;a href=&quot;http://flying-lotus.com/youre-dead/&quot;&gt;&lt;em&gt;You&#39;re Dead!&lt;/em&gt;&lt;/a&gt;, is denser than his previous album but maintains the latter&#39;s dreamlike haze and submerged thumps.&lt;/p&gt;
&lt;p&gt;Now seems like the perfect time to give Zola Jesus&#39; &lt;a href=&quot;http://en.wikipedia.org/wiki/Taiga_%28Zola_Jesus_album%29&quot;&gt;&lt;em&gt;Taiga&lt;/em&gt;&lt;/a&gt; a listen.&lt;/p&gt;
</content
		>
	</entry>
		
	<entry>
		<title>How to create a SVG icon system</title>
		<link href="https://danburzo.ro/svg-icon-system/" />
		<updated>2014-10-17T00:00:00Z</updated>
		<id>https://danburzo.ro/svg-icon-system/</id>
		<content type="html"
			>&lt;p&gt;Wherein I describe a pretty nice workflow for creating and maintaining an SVG icon set that will make it easy to tweak/extend your set of icons in the comfort of your favorite vector graphics application (as long as your favorite vector graphics application happens to be Adobe Illustrator) while at the same time providing the tools to generate the SVG assets automatically.&lt;/p&gt;
&lt;p&gt;The gist:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Keep your icons in a single .AI file with multiple artboards, and export each artboard as SVG;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;grunt-svgstore&lt;/code&gt; to clean up and merge the SVG files and &lt;code&gt;grunt-svginjector&lt;/code&gt; to generate a JavaScript file that you can use to inject the SVGs into your page;&lt;/li&gt;
&lt;li&gt;Write minimal CSS to lay out and color your icons;&lt;/li&gt;
&lt;li&gt;Enjoy the awesomeness of crisp, responsive icons.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s do this.&lt;/p&gt;
&lt;h2&gt;The case for SVG icons&lt;/h2&gt;
&lt;p&gt;People in the ongoing discussion around the best way to use icons in websites and web apps tend to agree that SVG is the optimal solution in many cases. I won&#39;t cover it here, but &lt;a href=&quot;http://css-tricks.com/svg-symbol-good-choice-icons/&quot;&gt;Chris Coyier&#39;s article&lt;/a&gt; explains well why we want to define our icons as named &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt; elements that we can then reference like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;svg&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;icon&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;use&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xlink:&lt;/span&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#icon-iconName&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Design your icons&lt;/h2&gt;
&lt;p&gt;To work on your icons side by side and have them neatly packaged in a single .AI file, &lt;em&gt;use multiple artboards&lt;/em&gt;--one for each icon in your set.&lt;/p&gt;
&lt;p&gt;In the &lt;kbd&gt;New Document&lt;/kbd&gt; screen, enter the number of artboards and choose other sensible settings (e.g. &lt;kbd&gt;✓ Align New Objects to Pixel Grid&lt;/kbd&gt;)&lt;/p&gt;
&lt;p&gt;In the &lt;kbd&gt;Artboards&lt;/kbd&gt; pane (if it&#39;s not shown, go in and check &lt;kbd&gt;Window → ✓ Artboards&lt;/kbd&gt;) you can add, remove and rearrange the artboards in your file. Make sure each artboard has the appropriate name, as this will ultimately become the name of your icon in your icon set.&lt;/p&gt;
&lt;h3&gt;Pro tip interlude&lt;/h3&gt;
&lt;p&gt;Whenever possible, it&#39;s a good idea to make your icon &lt;em&gt;a single compound path&lt;/em&gt;, which basically takes the various &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;rect&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;polygon&amp;gt;&lt;/code&gt; etc. elements and merges them into a single &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;. This gives you a slimmer SVG and you&#39;ll avoid Firefox rendering quirks — I&#39;ve seen it antialias some of the paths and leave others aliased and it just makes the icon look half-assed which is super depressing.&lt;/p&gt;
&lt;p&gt;In Illustrator, you create a compound path by selecting your shapes and hitting &lt;kbd&gt;⌘8&lt;/kbd&gt; on your keyboard. Use &lt;kbd&gt;⌥⇧⌘8&lt;/kbd&gt; to release a compound path if you find you need to make changes to individual paths. Both actions are accessible under &lt;kbd&gt;Object → Compound Path&lt;/kbd&gt;.&lt;/p&gt;
&lt;h3&gt;Export time!&lt;/h3&gt;
&lt;p&gt;Fast-forward to the moment you&#39;re happy with the set of icons and/or working inside Adobe Illustrator becomes unbearable and it&#39;s time to export those babies as separate SVG files. Granted it&#39;s a bit obtuse, since we piggyback off the &lt;em&gt;Save a copy...&lt;/em&gt; functionality to obtain that which might more accurately be described as an export, but I haven&#39;t found a more straightforward way to do it:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;File → Save a copy… → Format: SVG (svg), Use artboards: All&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;On the &lt;kbd&gt;SVG Options&lt;/kbd&gt; screen the default settings (i.e. &lt;kbd&gt;Profile: SVG 1.1&lt;/kbd&gt;) are fine. Each artboard will be saved as a separate SVG file with the name &lt;kbd&gt;fileName_artboardName.svg&lt;/kbd&gt;.&lt;/p&gt;
&lt;h2&gt;Clean up and package the icons&lt;/h2&gt;
&lt;p&gt;Next, we take all the little individual SVG files and package them into a single SVG file. For that we will need &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt; and a plugin called &lt;a href=&quot;https://github.com/FWeinb/grunt-svgstore&quot;&gt;grunt-svgstore&lt;/a&gt;. If you haven&#39;t used Grunt before, you can check out &lt;a href=&quot;http://danburzo.ro/grunt/chapters/getting-started&quot;&gt;this short introduction&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&#39;s how our Gruntfile should look:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;grunt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	grunt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;loadNpmTasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;grunt-svgstore&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	grunt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;svgstore&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;icons&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token string-property property&quot;&gt;&#39;icons.svg&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;icons/*.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token comment&quot;&gt;/*
            prefix all icons with an unambiguous label
          */&lt;/span&gt;
					&lt;span class=&quot;token literal-property property&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;icon-&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

					&lt;span class=&quot;token comment&quot;&gt;/* 
            cleans fill, stroke, stroke-width attributes 
            so that we can style them from CSS
          */&lt;/span&gt;
					&lt;span class=&quot;token literal-property property&quot;&gt;cleanup&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

					&lt;span class=&quot;token comment&quot;&gt;/*
            write a custom function to strip the first part
            of the file that Adobe Illustrator generates 
            when exporting the artboards to SVG
          */&lt;/span&gt;
					&lt;span class=&quot;token function-variable function&quot;&gt;convertNameToId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
						&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^&#92;w+&#92;_&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need a way to include the &lt;code&gt;icons.svg&lt;/code&gt; file in our web page. Rather than copying the content and inlining it in our HTML, we can include it via a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag (making it easier to maintain and a candidate for browser caching), for which we can use &lt;a href=&quot;https://github.com/danburzo/archived/tree/main/grunt-svginjector&quot;&gt;grunt-svginjector&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;grunt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	grunt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;loadNpmTasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;grunt-svgstore&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	grunt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;loadNpmTasks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;grunt-svginjector&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	grunt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;svgstore&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;icons&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token string-property property&quot;&gt;&#39;icons.svg&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;icons/*.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token literal-property property&quot;&gt;cleanup&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;token function-variable function&quot;&gt;convertNameToId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
						&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^&#92;w+&#92;_&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token literal-property property&quot;&gt;svginjector&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token literal-property property&quot;&gt;icons&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token string-property property&quot;&gt;&#39;icons.js&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;icons.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token literal-property property&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;icon-container&#39;&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	grunt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;icons&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;svgstore:icons&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;svginjector:icons&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our HTML we need a container into which we&#39;ll inject the content of &lt;code&gt;icons.svg&lt;/code&gt; and then we can just include the script:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;icon-container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;icons.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since the script inserts the content immediately, you must make sure the script is always included &lt;em&gt;after&lt;/em&gt; the container in your DOM.&lt;/p&gt;
&lt;p&gt;We&#39;ve registered a task called &lt;code&gt;icons&lt;/code&gt; to execute the svgstore and svginjector steps in order, so we can run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ grunt icons&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...to prep our icons for usage.&lt;/p&gt;
&lt;h2&gt;Use and customize your icons&lt;/h2&gt;
&lt;p&gt;Some final touches and we&#39;re done. Firstly, we need to hide the icon container so it does not take space in the layout.&lt;/p&gt;
&lt;p&gt;Remember that we set &lt;code&gt;cleanup: true&lt;/code&gt; in &lt;code&gt;grunt-svgstore&lt;/code&gt; to strip all presentational attributes like &lt;code&gt;fill&lt;/code&gt;, &lt;code&gt;stroke&lt;/code&gt; or &lt;code&gt;stroke-width&lt;/code&gt;. This is a necessity if we want to control these attributes from our CSS, since no attribute defined inside the &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt; elements can be overridden when we &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; them. To make sure everything looks okay, we&#39;ll define a stroke and a fill for the icons.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* 
  Hide the icon container, because the injected SVG 
  takes up physical space.
*/&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;#icon-container&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/*
  Set up the size, layout and colors.
*/&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.icon&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inline-block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.icon use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #000&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, as promised, whenever you need to use an icon on your web page:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;svg&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;icon&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;use&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xlink:&lt;/span&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#icon-myicon&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Chris Coyier&#39;s &lt;a href=&quot;http://css-tricks.com/mega-list-svg-information/&quot;&gt;A Compendium of SVG Information&lt;/a&gt; has got you covered: see section &lt;em&gt;SVG as an Icon System&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/willianjusten/awesome-svg#icons&quot;&gt;awesome-svg&lt;/a&gt; is a Github repository of links to SVG-related articles and resources.&lt;/li&gt;
&lt;/ul&gt;
</content
		>
	</entry>
</feed>