Dan Cătălin Burzo2024-01-03T00:00:00Zhttps://danburzo.ro/Dan BurzoWSF 462024-01-03T00:00:00Zhttps://danburzo.ro/watchstarfork/2024-01-03/<p><em>In what is probably the heftiest, most profusely procrastinated edition so far, here are some of the links I’ve been hoarding these past few months.</em></p>
<h2>News</h2>
<p><strong><abbr>WCAG</abbr> 2.2</strong>, the latest version of the Web Content Accessibility Guidelines specification, has been promoted to a <a href="https://www.w3.org/TR/WCAG22/"><abbr>W3C</abbr> Recommendation</a>. It adds <a href="https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/">nine new success criteria</a>, which Patrick H. Lauke unpacks in <a href="https://tetralogical.com/blog/2023/10/05/whats-new-wcag-2.2/">What’s new in <abbr>WCAG</abbr> 2.2</a>.</p>
<p><strong>Firefox.</strong> The <abbr>CSS</abbr> math functions <code>abs</code>, <code>sign</code>, <code>round</code>, <code>mod</code>, <code>rem</code>, <code>pow</code>, <code>sqrt</code>, <code>hypot</code>, <code>log</code>, and <code>exp</code> shipped in <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/118">Firefox 118</a>. A couple of uses come to mind: the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/sqrt"><code>sqrt()</code></a> function is useful for <a href="https://danburzo.ro/aspect-ratio-size/">sizing images based on their aspect ratio</a>, and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/round"><code>round()</code></a> can help with rhythmic sizing and positioning while <a href="https://drafts.csswg.org/css-rhythm/">the dedicated spec</a> is still being developed:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.column</span> <span class="token punctuation">{</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">round</span><span class="token punctuation">(</span>100%<span class="token punctuation">,</span> 100px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/120">Firefox 120</a> brings back media queries for <code><video></code> sources. Scott Jehl, who <a href="https://scottjehl.com/posts/responsive-video/">submitted the patch</a>, writes about <a href="https://scottjehl.com/posts/using-responsive-video/">How to Use Responsive <abbr>HTML</abbr> Video (...and Audio!)</a>.</p>
<p>With this release, the <code>lh</code> and <code>rlh</code> <abbr>CSS</abbr> units became supported across major browser engines. A while back I wrote about <a href="https://danburzo.ro/line-height-lh/">some possible uses for line-height units</a>.</p>
<p>Also universally available now that <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/121">Firefox 121</a> is released:</p>
<ul>
<li>the <code>:has()</code> <abbr>CSS</abbr> pseudo-class; get up to speed with resources compiled by Ryan Mulligan at the end of his celebratory post, <a href="https://ryanmulligan.dev/blog/we-can-has-it-all/">We can <code>:has</code> it all</a>.</li>
<li>lazy-loading for <code><iframe></code> elements.</li>
</ul>
<p>Something I wasn’t aware A. is a thing B. needs fixing, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse"><code>Date.parse()</code></a> now accepts more non-standard date formats — probably to align Firefox with other browsers as an Interop goal?</p>
<p><strong>Chrome.</strong> With their addition to <a href="https://developer.chrome.com/en/blog/new-in-chrome-119/">Chrome 119</a>, the <code>:user-valid</code> and <code>:user-invalid</code> pseudo-classes — like <code>:valid</code> and <code>:invalid</code>, but useful — are now universally supported.</p>
<p>Also included in the release is the relative color syntax for <abbr>CSS</abbr> colors. Like <a href="https://danburzo.ro/watchstarfork/2023-05-19/#safari-16-4">Safari 16.4’s implementation earlier this year</a>, Chrome’s has <a href="https://danburzo.ro/demos/color/relative-color-syntax.html">some rough edges</a>, but seems otherwise comprehensive.</p>
<p><a href="https://developer.chrome.com/blog/new-in-chrome-120">Chrome 120</a> debuts a mechanism to turn <code><details></code> elements into exclusive accordion widgets, by having them share the <code>name</code> attribute, as explained by <a href="https://open-ui.org/components/accordion.explainer/">Open <abbr>UI</abbr></a>. This is also the pattern used for grouping form controls.</p>
<p><a href="https://webkit.org/blog/14787/webkit-features-in-safari-17-2/"><strong>Safari 17.2</strong></a> shares some features with recent versions of Firefox and Chrome, shipping with with exclusive accordions, the relaxed syntax for <abbr>CSS</abbr> nesting, the <code>lh</code> and <code>rlh</code> units (along with other font-related units), and <abbr>CSS</abbr> math functions.</p>
<p>It’s also the first browser to support the new syntax for <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"><code>import</code> attributes</a>.</p>
<p>Speaking of recent releases, Patrick Brosset has made a handy little reference page for tracking <a href="https://patrickbrosset.com/lab/browser-versions/">browser versions</a>.</p>
<p><strong>Baseline</strong>, whose debut <a href="https://danburzo.ro/watchstarfork/2023-05-19/">a few months ago</a> was met with some apprehension, <a href="https://web.dev/blog/baseline-definition-update">has revised its labels</a> with a higher (temporal) bar for web features to be considered „safe to use”. <a href="https://developer.mozilla.org/en-US/blog/baseline-evolution-on-mdn/">Widgets on <abbr>MDN</abbr></a> now show features as:</p>
<blockquote>
<p><em>Newly available</em>. The feature is marked as interoperable from the day the last core browser implements it. It marks the moment when developers can start getting excited and learning about a feature.</p>
<p><em>Widely available</em>. The feature is marked as having wider support thirty months or 2.5 years later. It marks the moment when it's safe to start using a feature without explicit cross-browser compatibility knowledge.</p>
</blockquote>
<p>Adrian Roselli is quick to point out — given that the accessibility story of some web features is often complicated, and not adequately captured in the data used for assigning these labels — that <a href="https://adrianroselli.com/2023/12/baseline-does-not-really-cover-baseline-support.html">Baseline Does Not Really Cover Baseline Support</a>.</p>
<p><strong>Servo.</strong> Having joined the Linux Foundation Europe earlier this year, the experimental web rendering engine written in Rust is seeing steady progress. <a href="https://servo.org/blog/2023/11/30/embedding-floats-color-mix/">This month in Servo: better floats, <code>:has()</code>, <code>color-mix()</code>, and more!</a>.</p>
<p><strong>ActivityPub in WordPress.</strong> Although <a href="https://wordpress.com/blog/2023/10/11/activitypub/">the announcement</a> focuses on WordPress <em>.com</em>, self-hosted WordPress instances can join the fediverse through <a href="https://wordpress.org/plugins/activitypub/">an official plugin</a>.</p>
<p><strong>Kirby,</strong> which has fastly become my <a href="https://danburzo.ro/toolbox/cms/"><abbr>CMS</abbr> of choice</a> for projects that require a visual admin interface, has had a new major release with <a href="https://getkirby.com/releases/4.0?party">Kirby 4</a>. Solid stuff.</p>
<h2>Articles</h2>
<h3>Software engineering, math</h3>
<p>Jake Lazaroff with a three-article series on conflict-free replicated data types (<abbr>CRDT</abbr>): from <a href="https://jakelazaroff.com/words/an-interactive-intro-to-crdts/">An Interactive Intro</a> to <a href="https://jakelazaroff.com/words/building-a-collaborative-pixel-art-editor-with-crdts/">Building a Collaborative Pixel Art Editor</a> and finally <a href="https://jakelazaroff.com/words/making-crdts-98-percent-more-efficient/">Making <abbr>CRDT</abbr>s 98% More Efficient</a>.</p>
<p><a href="https://github.com/stickfigure/blog/wiki/How-to-%28and-how-not-to%29-design-REST-APIs">How to (and how not to) design <abbr>REST</abbr> <abbr>API</abbr>s</a> by Jeff Schnitzer: <q>In my career, I have consumed hundreds of <abbr>REST</abbr> <abbr>API</abbr>s and produced dozens. Since I often see the same mistakes repeated in <abbr>API</abbr> design, I thought it might be nice to write down a set of best practices.</q></p>
<p><a href="https://www.youtube.com/watch?v=htYh-Tq7ZBI">Why can’t you multiply vectors?</a>, a talk by Freya Holmér at Dutch Game Day 2023.</p>
<h3>CSS</h3>
<p><strong>An absolute unit.</strong> Ashlee M Boyer has been exploring how the choice of <abbr>CSS</abbr> units affects the accessibility of web pages and has advice: <a href="https://ashleemboyer.com/blog/don-t-use-fixed-css-height-or-width-on-text-containers">don’t use fixed <abbr>CSS</abbr> <code>height</code> or <code>width</code> on buttons, links, or any other text containers</a>, but <a href="https://ashleemboyer.com/blog/why-you-should-use-px-units-for-margin-padding-and-other-spacing-techniques">you should use <code>px</code> units for margin, padding, & other spacing techniques</a>.</p>
<p>Relatedly, I’ve written on why <a href="https://danburzo.ro/media-query-units/">you should prefer <code>em</code>s for media queries</a>.</p>
<p>Šime Vidas looked at the implementations of the small, large and dynamic viewport units to conclude that <a href="https://www.smashingmagazine.com/2023/12/new-css-viewport-units-not-solve-classic-scrollbar-problem/">New <abbr>CSS</abbr> Viewport Units Do Not Solve The Classic Scrollbar Problem</a>.</p>
<p><strong><abbr>CSS</abbr> hacks.</strong> Jane Ori discovered that while <code>tan(atan2(…))</code> is still a bit buggy in browsers, it essentially allows <a href="https://dev.to/janeori/css-type-casting-to-numeric-tanatan2-scalars-582j"><abbr>CSS</abbr> type casting to numeric</a>, useful for many techniques.</p>
<p>Clever use of the <code>none</code> keyword in color components lets <code>color-mix()</code> produce relative colors, as shown by Lea Verou in <a href="https://codepen.io/leaverou/pen/gOZZQZb?editors=1100">Emulate basic relative color syntax with <code>color-mix()</code> + the <code>none</code> keyword</a>.</p>
<p>Bramus demonstrates how to leverage the scroll speed and direction in scroll-driven animations. <a href="https://www.bram.us/2023/10/23/css-scroll-detection/">Solved by <abbr>CSS</abbr> Scroll-Driven Animations: Style an element based on the active Scroll Direction and Scroll Speed</a>.</p>
<p><strong>The new layout.</strong> In <a href="https://ishadeed.com/article/photoshop-web-css/"><abbr>CSS</abbr> Findings From Photoshop Web Version</a>, Ahmad Shadeed explores how the app uses flex and grid layout for its interface, most likely powered by Adobe’s <a href="https://spectrum.adobe.com/">Spectrum</a> design system.</p>
<p>Speaking of grid layout, Josh W. Comeau has published <a href="https://www.joshwcomeau.com/css/interactive-guide-to-grid/">An Interactive Guide to <abbr>CSS</abbr> Grid</a>, and Lene Saile has explored the topic of <abbr>CSS</abbr> subgrids in <a href="https://www.lenesaile.com/en/blog/about-subgrid-and-colored-grid-lines/">About subgrid and colored grid lines</a> and <a href="https://www.lenesaile.com/en/blog/inheriting-grid-dimensions-from-siblings-with-subgrid/">“Inheriting” grid dimensions from siblings with subgrid</a>.</p>
<p><a href="https://developer.mozilla.org/en-US/blog/getting-started-with-css-container-queries/">Getting started with <abbr>CSS</abbr> container queries</a>, a refresher from Michelle Barker.</p>
<h3>Performance</h3>
<p><a href="https://csswizardry.com/2023/10/the-three-c-concatenate-compress-cache/">The Three Cs: Concatenate, Compress, Cache</a> by Harry Roberts: <q>In the current landscape, bundling is still a very effective strategy. Larger files compress much more effectively and thus download faster at all connection speeds. Further, queueing, scheduling, and latency work against us in a many-file setup. However, one huge bundle would limit our ability to employ an effective caching strategy, so begin to conservatively split out into bundles that are governed largely by how often they’re likely to change. Avoid resending unchanged bytes.</q></p>
<p><a href="https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/">Speeding up the JavaScript ecosystem: The barrel file debacle</a> is the seventh of a series where Marvin Hagemeister looks at performance bottlenecks in JavaScript projects, this time focusing on <em>barrel files</em>, i.e. files that only export other files. <q>So if you work on a project which uses barrel files extensively, there is a free optimization you can apply that makes many tasks 60-80% faster: Get rid of all barrel files.</q></p>
<h3>Usability, accessibility, interaction design</h3>
<p>Having tackled scrolljacking in an earlier article, Sara Ramaswamy now turns in the results of user testing for a related affectation: <a href="https://www.nngroup.com/articles/scroll-fading-101/">Scroll Fading 101</a>. The summary concedes that <q>when used right, this design pattern can improve brand perception, optimize page loading, and make content more digestible</q>, but the takeaway is that <q>scroll fading can cause serious usability issues when used incorrectly</q>.</p>
<p>Another word of advice from Nielsen Norman Group in <a href="https://www.nngroup.com/articles/content-dispersion/">The Negative Impact of Mobile-First Web Design on Desktop</a>: <q>Mobile-first web designs cause significant usability issues when viewed on desktop. Content becomes overly dispersed across long scrolling pages with expansive white space and enlarged images and fonts, making it difficult for users to consume and understand the information.</q></p>
<p><a href="https://lea.verou.me/blog/2023/minimalist-affordances/">Minimalist Affordances: Making the right tradeoffs</a> by Lea Verou: <q>A common incarnation of form-over-function, is when designers start identifying signifiers and affordances as noise to be eliminated, sacrificing a great deal of learnability for an — often marginal — improvement in aesthetics.</q></p>
<p>Still from Lea Verou, <a href="https://lea.verou.me/blog/2023/eigensolutions/">Eigensolutions: composability as the antidote to overfit</a>: <q>Overfitting happens when solutions don’t generalize sufficiently and is a hallmark of poor design. Eigensolutions are the opposite: solutions that generalize so much they expose links between seemingly unrelated use cases. Designing eigensolutions takes a mindset shift from linear design to composability.</q></p>
<p>Pirijan shares <a href="https://pketh.org/design-principles.html">Kinopio’s Design Principles</a>.</p>
<p>Julia Evans clarifies some <a href="https://jvns.ca/blog/2023/11/01/confusing-git-terminology/">Confusing git terminology</a>: <q>I’ve done my best to explain what’s going on with these terms, but they cover basically every single major feature of git</q>. Sounds about right.</p>
<p><a href="https://www.tpgi.com/the-road-to-accessible-drag-and-drop-part-1/">The Road to Accessible Drag and Drop</a>, an in-depth, three-part series by James Edwards on developing an inclusive pattern for moving items between lists (e.g. from one column to another on a Kanban board).</p>
<h3><abbr>HTML</abbr>, web platform</h3>
<p><strong>Significant markup.</strong> <a href="https://www.htmhell.dev/adventcalendar/2023/1/">The <abbr>UX</abbr> of <abbr>HTML</abbr></a> by Vasilis van Gemert: <q>Recently I decided to stop using the word <em>semantics</em>. Instead I talk about the <abbr>UX</abbr> of <abbr>HTML</abbr>. And all of a sudden my students are not allergic to <abbr>HTML</abbr> anymore but really interested.</q></p>
<p><a href="https://open-ui.org/components/invokers.explainer/">Invokers</a> is an interesting web platform proposal from Keith Cirkel via Open <abbr>UI</abbr>. It’s a way to declare behaviors in <abbr>HTML</abbr> directly, by having buttons dispatch events to a target with the <code>invoketarget</code> / <code>invokeaction</code> attributes. The first use cases planned for it are controlling popovers and dialogs, which would act upon the <code>InvokeEvent</code>s they receive from buttons without any JavaScript code. In the general case, it could also be used as a basic version of <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent">custom events</a> that you can hook up declaratively.</p>
<p><strong>Add to dock.</strong> Some experiences with the new web app feature in macOS Sonoma / Safari 17: from Jeremy Keith as a user of <a href="https://adactio.com/journal/20520">Websites in the dock</a>, and from Mark Otto as a creator of <a href="https://markdotto.com/2023/10/01/macos-web-apps/">macOS web apps</a>.</p>
<p>Over on Windows 11, Aaron Gustafson demonstrates how <abbr>PWA</abbr>s can provide cards in the dashboard: <a href="https://www.aaron-gustafson.com/notebook/widgets/">Widgets!</a>.</p>
<p><strong>The belly of the beast.</strong> Nicole Sullivan digs into source code to explain how different browsers assist users in tapping things: <a href="https://www.stubbornella.org/2023/09/17/expanding-your-touch-targets/">Expanding your touch targets</a>.</p>
<p>A bag of (non-obvious) debugging tricks by Alan Norbauer: <a href="https://alan.norbauer.com/articles/browser-debugging-tricks">67 Weird Debugging Tricks Your Browser Doesn't Want You to Know</a>.</p>
<h3>JavaScript, web components</h3>
<p><a href="https://www.baldurbjarnason.com/2023/web-dev-without-node/">How do you even web dev without node?</a>, a <q>quick introduction to test-driven web development using just the browser</q> by Baldur Bjarnason, a prelude to his upcoming course called <em>Uncluttered Test-Driven Web Development</em>.</p>
<p>The <a href="https://tc39.es/proposal-temporal/docs/">Temporal</a> proposal, now in Stage 3, aims to solve longstanding shortcomings of JavaScript Date objects. Taro gives the rundown in <a href="https://taro.codes/posts/2023-08-23-temporal-api/">Temporal <abbr>API</abbr> is Awesome</a>.</p>
<p><a href="https://nolanlawson.com/2023/12/02/lets-learn-how-modern-javascript-frameworks-work-by-building-one/">Let’s learn how modern JavaScript frameworks work by building one</a>, an exercise by Nolan Lawson in building <q>a framework from the post-React era</q> from scratch.</p>
<p><strong><abbr>HTML</abbr> Web components.</strong> There has been some recent fervor around a particular flavor of web component, one that works by augmenting its <abbr>HTML</abbr> content in the Light <abbr>DOM</abbr>. After shifting links around for a solid hour in an effort to fit the many recent posts in a coherent sequence, I’ll just defer to Chris Coyier, who has conveniently already done so in <a href="https://frontendmasters.com/blog/light-dom-only/">Light-<abbr>DOM</abbr>-Only Web Components are Sweet</a>.</p>
<p>In <a href="https://bkardell.com/blog/LovelyTrees.html">Lovely trees</a>, Brian Kardell acknowledges the seeming mismatch between the trade-offs in the design of Shadow <abbr>DOM</abbr> and the things people want it to do, while inviting experimentation for figuring out the middle ground(s).</p>
<p>Thomas Wilburn highlights the advantages of Shadow <abbr>DOM</abbr> in <a href="https://www.milezero.org/index.php/tech/web/components/chiaroscuro.html">Chiaroscuro, or Expressive Trees in Web Components</a>: <q>By combining slots, shadow DOM, and markup patterns, we can embed a language in HTML that produces either abstract data structures, user interface, or both. Without adding any browser plugins, we're able to manipulate this tree just using the dev tools, so we can easily experiment with our application, and it's compatible with our existing editor tooling too.</q></p>
<h3>Type design</h3>
<p><a href="https://letterformarchive.org/shop/unexpected-baskerville-the-story-of-lovefrom-serif/">Unexpected Baskerville: The Story of LoveFrom Serif</a> (video), a Letterform Lecture with Antonio Cavedoni and Chris Wilson that <q>delves into the process of creating a new interpretation of Baskerville’s letters, exploring surviving punches, printed books, and contemporary writing master’s manuals</q>.</p>
<p><a href="https://www.abyme.net/revue/thefirstsanseriftype/">The first sanserif type</a>, <q>a slightly revised and newly illustrated version</q> of <em>Caslon’s Egyptian: the first sanserif type</em>, an article by James Mosley originally published in 1988.</p>
<h2>Tools and resources</h2>
<p>From Greg Wilson of <a href="https://aosabook.org/en/">Architecture of Open Source Applications</a> fame, <em>Sofware Design by Example</em> is out in a <a href="https://third-bit.com/sdxpy/">Python edition</a>, having been previously written <a href="https://third-bit.com/sdxjs/">with JavaScript examples</a>.</p>
<p><a href="https://wholeearth.info/">Whole Earth Index</a>, <q>a nearly-complete archive of Whole Earth publications, a series of journals and magazines descended from the Whole Earth Catalog, published by Stewart Brand and the <abbr>POINT</abbr> Foundation between 1970 and 2002.</q></p>
<p>Michael Wörgötter, a Munich-based designer and educator, has donated a boxed set of over 600 typeface cards to the Letterform Archive. They are <a href="https://www.flickr.com/photos/letterformarchive/albums/72177720310834741">available on Flickr</a>, and new photographs for the online archive are to follow. More in <a href="https://letterformarchive.org/news/schriftenkartei-german-font-index/">This Just In: Schriftenkartei, a Typeface Index</a>.</p>
<p><a href="https://monaspace.githubnext.com/">Monaspace</a> is a superfamily of five monospaced typefaces designed to be mixed and matched to lend different voices to bits of source code. OpenType contextual alternates help it achieve a more uniform color, a feature it calls <a href="https://github.com/githubnext/monaspace/blob/main/docs/Texture%20Healing.md">texture healing</a>. It was produced by Lettermatic for GitHub and released under the <abbr>OFL</abbr> license.</p>
<p><a href="https://github.com/TypeTogether/Playpen-Sans">Playpen Sans</a> <q>is one of the font families produced by TypeTogether after more than two years of primary research into handwriting education for Latin-based languages. It has seven automatic alternates for each character and a built-in shuffler that both ensures variation and avoids repetitive shapes in close proximity. This feature adds to the overall organic, spontaneous, and authentic feel of the handwritten style.</q> More on <a href="https://www.type-together.com/making-playpen-sans">Making Playpen Sans</a>.</p>
<p>The latest <a href="https://github.com/rsms/inter/releases/tag/v4.0">update</a> to Rasmus Andersson’s <a href="https://rsms.me/inter/">Inter</a> typeface packs over two years of work.</p>
<p><a href="https://ohnotypeschool.teachable.com/">Ohno Type School</a>, online type design courses from James Edmondson and Colin M. Ford: <q>Teaching has always been important to us, and Ohno Type School is our attempt to open the doors of our studio and share with students how we approach type design projects.</q> Some, like <a href="https://ohnotypeschool.teachable.com/p/essential-robofont">Essential RoboFont</a>, are available free of charge.</p>
<p>Fantastic advice compiled by Stephen Coles: <a href="https://typographica.org/on-typography/typeface-selection-resources/">Typeface Selection Resources</a>.</p>
<p><a href="https://automerge.org/blog/2023/11/06/automerge-repo/">Automerge-Repo: a ‘batteries-included’ toolkit for building local-first applications</a>. <q>Often, the first thing developers ask after discovering Automerge was how to connect it into an actual application.</q></p>
<p><a href="https://www.zachleat.com/web/is-land/"><code>is-land</code> Web Component</a> by Zach Leatherman: <q>Islands Architecture (apologies for this oversimplification) is turbo-charged lazy loading. It’s a way to initialize components and resources for sections (islands) of your web site when certain conditions are met: the island becomes visible, the page is idle, the viewport is a certain size, on events (click, mouseover, etc), and respecting user preferences (save data, reduce motion, etc).</q></p>
<hr />
<p><strong>A Romanian type specimen from 1894</strong> can be downloaded as a <abbr>PDF</abbr> <a href="https://www.europeana.eu/en/item/954/Culturalia_7f103336_006c_44a0_b017_03e07842729a">from Europeana</a>, digitized from the collection of the Cluj County Library. This may be the first <a href="https://llll.ro/typographica/probare/">local type specimen</a> to have been made widely available online.</p>
<p><strong>A mind-melting photo</strong> of Rembrandt’s <a href="https://www.rijksmuseum.nl/en/stories/operation-night-watch/story/ultra-high-resolution-photo">“The Night Watch”</a> which you can zoom into for incredible detail. The ultra-high-resolution composite is the result of Rijksmuseum’s <a href="https://www.rijksmuseum.nl/en/stories/operation-night-watch">Operation Night Watch</a>. It reminds me of Tacita Dean’s <a href="https://mackbooks.eu/products/buon-fresco-br-tacita-dean"><em>Buon fresco</em></a>.</p>
<p><strong>A handsome notebook</strong> ten years in the making: <a href="https://ia.net/topics/ia-writer-in-paper">iA Writer in Paper</a>.</p>
WSF 452023-09-23T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-09-23/<p><em>I was caught up with <abbr>IRL</abbr> things these past couple of months, so today’s edition packs almost a hundred links… oops!</em></p>
<hr />
<h2>News</h2>
<p><strong>Firefox.</strong> I was surprised to find among the <a href="https://www.mozilla.org/en-US/firefox/115.0/releasenotes/">Firefox 115 release notes</a> some movement around editing, a powerful yet slow-progressing web platform feature that’s still rife with underdocumented behavior:</p>
<blockquote>
<p>The builtin editor now behaves similarly to other browsers with <code>contenteditable</code> and <code>designMode</code> when splitting a node, e.g. typing <kbd>Enter</kbd> to split a paragraph, and also when joining two nodes, e.g. typing <kbd>Backspace</kbd> at the start of a paragraph to join the paragraph and the previous one. When a node is split, the builtin editor creates a new node after the original one instead of before, i.e. creates the right node instead of the left node. Similarly, when two nodes are joined, the builtin editor deletes the latter node and moves its children to the end of the preceding node instead of deleting the former node and moving its child to the start of the following node.</p>
</blockquote>
<p>Why now? Well, it seems that a few editing issues of particular interest have been included in <a href="https://wpt.fyi/results/editing/other?label=master&label=experimental&product=chrome&product=firefox&product=safari&aligned&view=interop&q=label%3Ainterop-2023-webcompat">Interop 2023</a>, which is such great news. I’ll take literally anything that can help bring about more robust rich-text editors to the web. The <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1819551">Mozilla#1819551</a> meta-issue tracks Firefox’s work towards cross-browser compatibility.</p>
<p>In JavaScript, methods to <a href="https://github.com/tc39/proposal-change-array-by-copy">change arrays by copy</a> provide the missing pieces for a complete non-mutating array <abbr>API</abbr>. With Firefox 115, these now have universal browser support.</p>
<p><a href="https://www.mozilla.org/en-US/firefox/116.0/releasenotes/">Firefox 116</a> fixes the ability to paste files copied from the operating system onto web pages. It only works for one file at a time [<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1389964">Mozilla#1389964</a>], but it’s a solid start.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/117">Firefox 117</a> brings <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting"><abbr>CSS</abbr> Nesting</a>, closing the gap on universal browser support for this syntax. It’s also the first browser to feature the relaxed syntax that allows nested selector lists to start with a type selector. Previously the spec required the <code>&</code> selector in front, to disambiguate the type selector from a property name, but the <abbr>CSS</abbr> parsing algorithm was adjusted to get rid of the limitation.</p>
<p><strong>Chrome.</strong> A first batch of Scroll-driven Animation features landed in <a href="https://developer.chrome.com/blog/new-in-chrome-115/">Chrome 115</a>. I’ve linked to <a href="https://danburzo.ro/watchstarfork/2023-07-02/#future-animation">early explorations</a> from the Canary era of these <abbr>API</abbr>s. More recently, Michelle Barker has built a set of fun demos for the <abbr>MDN</abbr> blog in <a href="https://developer.mozilla.org/en-US/blog/scroll-progress-animations-in-css/">Scroll progress animations in <abbr>CSS</abbr></a>, Dave Rupert has implemented <a href="https://daverupert.com/2023/08/animation-timeline-scroll-shadows/">scroll shadows with animation-timeline</a>, and Ryan Mulligan has written about <a href="https://ryanmulligan.dev/blog/scroll-driven-animations/">Starting Exploration of Scroll-driven Animations in CSS</a>.</p>
<p>Further animation helpers were enrolled in Chrome 116 and <a href="https://developer.chrome.com/en/blog/new-in-chrome-117/">Chrome 117</a>: the <code>display</code> property and elements’ entry and exit from the <abbr>DOM</abbr> can now be properly transitioned and keyframed, as summarized by Una Kravets and Joey Arhar in <a href="https://developer.chrome.com/blog/entry-exit-animations/">Four new <abbr>CSS</abbr> features for smooth entry and exit animations</a>.</p>
<p><strong>Safari 17</strong>, whose features <a href="https://danburzo.ro/watchstarfork/2023-07-02/#safari-17">I looked at</a> when the beta was announced, was made widely available as part of the iOS 17 release, with macOS to follow in a few days. Here are the <a href="https://webkit.org/blog/14445/webkit-features-in-safari-17-0/">WebKit release notes</a> from Jen Simmons & team.</p>
<p><strong>WordPress</strong> has turned twenty this year. <a href="https://wordpress.org/news/2023/08/lionel/">WordPress 6.3 Lionel</a> is <q>the culmination of years of work from hundreds of contributors, bringing a more powerful and cohesive editing experience for crafting websites with blocks</q>. Its standout feature is the realization of Full-Site Editing, where all aspects of the website can be tweaked visually. It’s relieving to learn from Matt Mullenweg’s talk on <a href="https://www.youtube.com/watch?v=EavRd7PtA0Q">what’s next for Gutenberg</a> that core support for multilingual websites is among the priorities. Still no mention of custom fields, though.</p>
<h3>Usability, accessibility</h3>
<p>With scroll-driven animations getting native <abbr>API</abbr>s, some research on their usability seems timely. Nielsen Norman Group’s <a href="https://www.nngroup.com/articles/scrolljacking-101/">Scrolljacking 101</a> concludes:</p>
<blockquote>
<p>Altering the normal pace or direction of scrolling can contradict user expectations, control, and freedom. If businesses adopt the pattern, they can minimize usability risks by weighing it against functional value, cognitive load, and user efficiency.</p>
</blockquote>
<p>Still from <abbr>NNG</abbr>, another reminder that you are not your users. They found that <q>across 33 rich countries, only 5% of the population has high computer-related abilities, and only a third of people can complete medium-complexity tasks.</q> <a href="https://www.nngroup.com/articles/computer-skill-levels/">The Distribution of Users’ Computer Skills: Worse Than You Think</a>.</p>
<p>Take, for example, <a href="https://jenson.org/text/">The invisible problem</a> described by Scott Jenson: <q>Text editing on mobile isn’t ok. It’s actually much worse than you think, an invisible problem no one appreciates.</q></p>
<p>Adrian Roselli demonstrates a pattern for a <a href="https://adrianroselli.com/2023/08/progressively-enhanced-html-accordion.html">Progressively Enhanced <abbr>HTML</abbr> Accordion</a> based on a set of <code><details></code> elements with JavaScript to ensure at most one is open at any time. Exclusive accordions are <a href="https://open-ui.org/components/accordion.explainer/">an active area of exploration</a> for Open <abbr>UI</abbr>.</p>
<p>Adrian also explains <a href="https://adrianroselli.com/2023/08/where-to-put-focus-when-deleting-a-thing.html">Where to Put Focus When Deleting a Thing</a>: <q><abbr>TL;DR</abbr>: When deleting something you should generally move focus to the prior equivalent control or its grouping container.</q></p>
<p>Joel Holmberg: <q>You’ve all seen them, tiny switches that let you toggle a setting. And maybe, just like me, you sometimes pause, thinking <em>…Is it on or off?</em> That’s because <a href="https://axesslab.com/toggles-suck/">toggles suck!</a></q>.</p>
<p>Gerardo Rodriguez has written a four-part series on all that goes into building progressively-enhanced form validation. <a href="https://cloudfour.com/thinks/progressively-enhanced-form-validation-part-1-html-and-css/">Here’s the first part</a>, with the others linked at the end of the article.</p>
<p><abbr>GIF</abbr> on the web are on their way out, slowly being replaced by better technology. Tyler Sticka shows how to use <code><video></code> to build <a href="https://cloudfour.com/thinks/accessible-animated-gif-alternatives/">Accessible Animated <abbr>GIF</abbr> Alternatives</a>.</p>
<p>Another thing I’d like to give the boot to are my outdated, <abbr>XHTML</abbr>-era, markup habits. Jake Archibald makes a compelling <a href="https://jakearchibald.com/2023/against-self-closing-tags-in-html/">case against self-closing tags in <abbr>HTML</abbr></a>.</p>
<h3>CSS</h3>
<p>The search for idiomatic patterns for sidenotes continues. Eric A. Meyer shows how to turn endnotes into sidenotes with anchor positioning in <a href="https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/">Nuclear Anchored Sidenotes</a>. The bit of <abbr>CSS</abbr> to prevent the anchored notes from overlapping is especially ingenious.</p>
<p><a href="https://www.oddbird.net/2023/07/05/contain-root/">Can We Query the Root Container?</a> by Miriam Suzanne: <q>I spoke about Container Queries at both Smashing Conference (San Francisco) and CSS Day (Amsterdam) – where I recommended setting up a root container to replace most media queries. Since then, Temani Afif pointed out a few issues with that approach, and sent me down a rabbit hole of overlapping specs and browser bugs.</q></p>
<p>Ahmad Shadeed explores the intricacies of <a href="https://ishadeed.com/article/virtual-keyboard-api/">the virtual keyboard <abbr>API</abbr></a> for making sure web content plays nice with the onscreen keyboard on mobile.</p>
<p>Andy Bell has refined <a href="https://andy-bell.co.uk/a-more-modern-css-reset/">A (more) Modern <abbr>CSS</abbr> Reset</a>, a succinct set of default style fixes. It motivated me to write down <a href="https://danburzo.ro/snippets/css-reset/">my own snippet</a>.</p>
<h3>JavaScript</h3>
<p><a href="https://joshcollinsworth.com/blog/antiquated-react">Things you forgot (or never knew) because of React</a>, Josh Collinsworth on what makes React legacy software: <q>While React undoubtedly helped to proliferate these ideas, it would be silly to consider React the ideal implementation of them. Great things are created through iteration, and for the most part, other choices in the frontend space that came later have the distinct advantage of iterating on top of the core ideas of React.</q></p>
<p><a href="https://nolanlawson.com/2023/08/23/use-web-components-for-what-theyre-good-at/">Use web components for what they’re good at</a> by Nolan Lawson: <q>I think web components have strengths and weaknesses, and you have to understand the tradeoffs before deciding when to use them. So let’s explore some cases where web components really shine, before moving on to where they might fall flat.</q></p>
<p>Manuel Matuzović outlines the <a href="https://www.matuzo.at/blog/2023/pros-and-cons-of-shadow-dom/">Pros and cons of using Shadow <abbr>DOM</abbr> and style encapsulation</a>: <q>I understand that most of the cons described in this post are not critical issues, and there are ways to work around them. The thing is, it's just a lot of stuff we have to consider, and we could avoid that by simply not using Shadow <abbr>DOM</abbr></q>. Manuel has also documented the accessibility aspects as questions and answers in <a href="https://www.matuzo.at/blog/2023/web-components-accessibility-faq/">Web Components Accessibility <abbr>FAQ</abbr></a>.</p>
<p><a href="https://www.spicyweb.dev/web-components-ssr-node/">Enhance vs. Lit vs. WebC or, How to Server-Render a Web Component</a> by Jared White: <q>Let's take a look at how to spin up a simple Node server and use custom elements as templates in three popular formats, and what this means for the future of web components.</q></p>
<p><a href="https://shopify.engineering/internationalization-i18n-best-practices-front-end-developers">Lessons From Linguistics</a>, a guide to internationalization for front-end developers by Lucas Huang. <q>Throughout this post, we’ll look at various ways that English grammar can be internalized in code, leading to errors in translation, and at the end I’ll provide some development best practices to help you avoid these common pitfalls.</q></p>
<p>Marc Grabanski with a whilwind tour of <a href="https://frontendmasters.com/blog/vanilla-javascript-reactivity/">Patterns for Reactivity with Modern Vanilla JavaScript</a>, from pubsub to signals.</p>
<h3>Security</h3>
<p>Images, both the raster and the vector kind, have been implicated in recent vulnerabilities. <span class="sc">CVE-2023-38633</span>, affecting the <code>librsvg</code> <abbr>SVG</abbr> processing library, is detailed by Zac Sims: <a href="https://www.canva.dev/blog/engineering/when-url-parsers-disagree-cve-2023-38633/">When URL parsers disagree</a>.</p>
<p><span class="sc">CVE-2023-4863</span>, a vulnerability in the <code>libwebp</code> package for rendering WebP images, has wider-reaching consequences, as explained by Alex Ivanovs in <a href="https://stackdiary.com/critical-vulnerability-in-webp-codec-cve-2023-4863/">Critical WebP bug: many apps, not just browsers, under threat</a>. I’ve updated macOS, web browsers, and any installed Electron-based applications, which I looked for with:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">find</span> /Applications <span class="token parameter variable">-iname</span> <span class="token string">"Electron Framework.framework"</span></code></pre>
<p>Despite the paucity of technical detail in the <abbr>CVE</abbr> disclosures, Ben Hawkes has produced a technical analysis and trigger for the overflow error that produces the vulnerability, explained in <a href="https://blog.isosceles.com/the-webp-0day/">The WebP 0day</a>.</p>
<p>In other despiriting news: <a href="https://www.theregister.com/2023/08/11/satellite_hacking_black_hat/">Want to pwn a satellite? Turns out it's surprisingly easy</a>, reports Iain Thomson for The Register.</p>
<h3>Performance</h3>
<p>A reminder by Jacob Groß: <a href="https://kurtextrem.de/posts/svg-in-js">Breaking Up with <abbr>SVG</abbr>-in-<abbr>JS</abbr> in 2023</a> (via <abbr>AR</abbr>). With the many ways to package and style <abbr>SVG</abbr>s available these days, bundling it as <abbr>JSX</abbr> is an avoidable performance penalty. My favorite technique is using <abbr>CSS</abbr> custom properties, as explained by Florens Verschelde: <a href="https://fvsch.com/svg-css-vars">Using <abbr>CSS</abbr> variables in <abbr>SVG</abbr> icons</a>.</p>
<p><a href="https://ericportis.com/posts/2023/view-transitions-break-incremental-rendering/">View Transitions Break Incremental Rendering</a>, warns Eric Portis. Not the feature itself, but <q>the folks prototyping cross-document View Transitions in Chrome are solving this fundamental problem by building and extending features which delay the first paint until the page is ready to transition.</q></p>
<p><a href="https://csswizardry.com/2023/07/core-web-vitals-for-search-engine-optimisation/">Core Web Vitals for Search Engine Optimisation: What Do We Need to Know?</a> by Harry Roberts: <q>Google’s Core Web Vitals initiative was launched in May of 2020 and, since then, its role in Search has morphed and evolved as roll-outs have been made and feedback has been received. However, to this day, messaging from Google can seem somewhat unclear and, in places, even contradictory. In this post, I am going to distil everything that you actually need to know using fully referenced and cited Google sources.</q></p>
<h3>Typography</h3>
<p><a href="https://www.typotheque.com/articles/originality-in-type-design">Originality in Type Design</a> by Peter Biľak: <q>The article was triggered by the discussions with the late Gerard Unger about the nature of originality in the type design industry, highlighting the importance of historical continuity in creating new works while examining the notions of originality and the boundaries between interpretation and plagiarism.</q></p>
<p>Leah Spencer on her work on <a href="https://www.alphabettes.org/type-revival-for-film-tv/">Type Revival for Film & <abbr>TV</abbr></a>: <q>The range of items we create is incredibly broad, and the cool thing about that is it reframes graphic design from an exclusive, professional pursuit into a universal human activity. If everything is design, everyone is a designer. So instead of creating as ‘Leah Spencer, graphic designer,’ I have to create as a shopkeeper, as a sign painter, as a college student, as an accountant, and so on.</q></p>
<p><q>There does not exist an official manual for the Riso printer. To fully understand this machine, you’ve got to get your hands dirty and try.</q> <a href="https://fontstand.com/news/essays/a-riso-printing-primer/">A Riso-printing primer</a> by Marte Verhaegen.</p>
<h3>Making software</h3>
<p><a href="https://doriantaylor.com/summer-of-protocols/">Retrofitting the Web</a> is an initiative by Dorian Taylor to build an engine that infuses ideas from hypermedia systems back into the web:</p>
<figure>
<blockquote>
<p><strong>Repairing a medium.</strong> Hypermedia has been an object of both theory and practice for decades preceding the Web—and even before computers. The Web—now ubiquitous—traded off a lot of really powerful ideas in return for not only easy implementation and deployment but also easy (instantaneous, global) publishing, plus one key capability the others categorically lacked: links that cross both system and organizational boundaries. But, there is nothing in principle preventing those powerful ideas from earlier hypermedia systems—real and imagined—from being grafted back onto the Web. It just needs a clear vision and a little elbow grease.</p>
</blockquote>
<figcaption>
<p>One of the three <q>points of departure</q> from the project’s <a href="https://doriantaylor.com/summer-of-protocols/motivation-and-rationale">Motivation and rationale</a>.</p>
</figcaption>
</figure>
<p>The reference implementation is called <a href="https://github.com/doriantaylor/rb-intertwingler">Intertwingler</a>, and you can learn more from <a href="https://www.youtube.com/watch?v=d5-lcvKfBM4">Dorian’s presentation</a> (30m video).</p>
<p><strong>Tinkering.</strong> I’ve always wanted to make Apple Wallet passes for the various pieces of plastic I have to carry around, and iliana etaoin describes just that in <a href="https://iliana.fyi/blog/ios-wallet-library-card/">Getting my library cards onto my phone the hard way</a>.</p>
<p>Harley Turan does interesting things with image metadata in <a href="https://hturan.com/writing/exploring-exif">Exploring <abbr>EXIF</abbr></a> by putting it in a database, building on top of an <a href="https://simonwillison.net/2020/May/21/dogsheep-photos/">earlier exploration</a> by master tinkerer Simon Willison.</p>
<p><a href="https://www.leebutterman.com/2023/06/01/offline-realtime-embedding-search.html">Wikipedia search-by-vibes</a> by Lee Butterman is <q>a browser-based search engine for Wikipedia, where you can search for ‘the reddish tall trees on the san francisco coast’ and find results like ‘Sequoia sempervirens’ (a name of a redwood tree). The browser downloads the database, and search happens offline.</q></p>
<p>New on <abbr>MDN</abbr>: <q>This intro-level tutorial walks thru all the steps of building a basic progressive web app, or <abbr>PWA</abbr>. We will be using web technologies — <abbr>HTML</abbr>, <abbr>CSS</abbr>, and JavaScript — to build a period tracking web app called <a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Tutorials/CycleTracker">CycleTracker</a></q>. Estelle Weyl <a href="https://openwebdocs.org/content/posts/cycletracker/">explains</a>: <q>If you are going to work thru a tutorial, you might as well work through one that is useful.</q></p>
<p><strong>Boring tech.</strong> Timo Tijhof describes <a href="https://timotijhof.net/posts/2023/an-internet-of-php/">An Internet of <abbr>PHP</abbr></a>: <q><abbr>PHP</abbr> is big. The trolls can proclaim its all-but-certain ‘death’ until the cows come home, but no amount of heckling changes that the Internet runs on <abbr>PHP</abbr>. The evidence is overwhelming. What follows is a loosely organised collection of precisely that evidence.</q></p>
<p>An underrated feature of WordPress and other old-school <abbr>CMS</abbr>es is they tend to have <abbr>RSS</abbr> on by default, and the feeds are often available even when the front-end fails to advertise them. Happy to see some of the tactics that have worked for me in the past listed in <a href="https://researchbuzz.me/2023/07/06/rss-2/"><abbr>RSS</abbr> for Post-Twitter News and Web Monitoring</a>.</p>
<p>Oh, and although it feels vaguely furtive — like someone has just forgotten to pull the plug on it — you can absolutely still subscribe to YouTube channels via <abbr>RSS</abbr>.</p>
<p><strong>New tech.</strong> Riccardo Mazzarini on the theoretical background, and implementation details of <a href="https://nomad.foo/blog/cola">cola: a text <abbr>CRDT</abbr> for real-time collaborative editing</a> written in Rust.</p>
<p>Relatedly, <a href="https://bricolage.io/some-notes-on-local-first-development/">Some notes on Local-First Development</a> by Kyle Mathews.</p>
<p><a href="https://ad8e.pages.dev/curve">New Bézier curves for vector graphics</a>, an exploration by Kevin Yin: <q>This article is about how math creates an easier-to-use version of Béziers. We'll show examples of issues with Béziers, then discuss the issues academically, then derive a curve that fixes the problems (and can draw circles!).</q></p>
<h2>Tools and resources</h2>
<p><a href="https://ateliertriay.github.io/bricolage/">Bricolage Grotesque</a> by Mathieu Triay is <q>a free and open source variable font with French attitude and British mannerisms across 3 axes: weight, width & optical size</q>.</p>
<p><a href="https://github.com/naipefoundry/gabarito">Gabarito</a> is <q>a light-hearted geometric sans typeface</q> initially designed in 2017 by Leandro Assis and Álvaro Franca for an online learning platform in Brazil, and released this year under an <abbr>OFL</abbr> license. Owing to its original function, Gabarito features <q>things like Logic and Set Theory symbols, scientific inferiors and superiors, extensive math operators, roman numerals and anything else a high-schooler may need for their homework.</q></p>
<p><a href="https://letterformarchive.org/news/a-toolkit-for-learning-type-history/">A Toolkit for Learning Type History</a> by the Letterform Archive: <q>Curated sets of objects in the Online Archive tell a visual story of typographic design, starting with the Western world.</q></p>
<p><a href="https://primarium.info/">Primarium</a> is <q>a groundbreaking educational effort by TypeTogether to document different models of handwriting that are taught to primary school students around the world</q> (<a href="https://danburzo.ro/watchstarfork/2023-07-02/#primarium">previously</a>).</p>
<p>Articles from four of the nine issues of <a href="http://typography.network/typographypapers/">Typography papers</a>, a publication developed by Paul Stiff at the Department of Typography & Graphic Communication at the University of Reading, are now available as <abbr>PDF</abbr>s, with more expected in the future.</p>
<p>From Ralf Herrmann, <a href="https://letterlibrary.org/">The Letter Library</a> is <q>a community website with the goal to create a catalog of all type specimens published by type foundries—printed or digital, past or present</q>.</p>
<p><a href="https://github.com/arrowtype/spacing">arrowtype/spacing</a>, <q>a collection of resources & recommendations that are helpful for spacing</q> fonts by Stephen Nixon of ArrowType.</p>
<p><a href="https://universaldesignguide.com/">Playbook for universal design</a>, a resource <q>providing easy access to planning and facilitating universal design development work, whether it is short workshops or longer work sessions</q>.</p>
<p>Mat Marquis’ <a href="https://javascript-for-web-designers.abookapart.com/"><em>JavaScript for Web Designers</em></a>, originally published by A Book Apart in 2016, is available as a free read.</p>
<p>Overture Maps Foundation <a href="https://overturemaps.org/overture-maps-foundation-releases-first-world-wide-open-map-dataset/">released its first world-wide open map dataset</a>, including places, buildings, transportation, and administrative boundaries (via <a href="https://simonwillison.net/2023/Jul/27/overture-maps/">Simon Willison</a>).</p>
<p><a href="https://viewports.fyi/">The ideal viewport doesn’t exist</a>, a visualization by Set Studio. <q>Before you settle on basing design decisions on a handful of strict breakpoints, make sure you consider the vast fragmentation of screen sizes and browser viewports.</q></p>
<p><a href="https://ijmacd.github.io/rfc3339-iso8601/">A Venn diagram of date and time formats</a>, according to <abbr>RFC</abbr> 3339 vs. <abbr>ISO</abbr> 8601 vs. <abbr>HTML</abbr>.</p>
<p>Giles Turnbull has a <a href="https://gilest.org/shoebox.html">Shoebox</a> on his website. <q>This is my digital shoebox: a bit of website for the odds-and-ends. I put things here so that I can delete them from other places.</q> What a nice idea, might do something similar.</p>
<h2>Today I learned</h2>
<p><strong>Disallow Chat<abbr>GPT</abbr></strong> from <a href="https://platform.openai.com/docs/gptbot">crawling your website</a> with these additions to your <code>robots.txt</code> file:</p>
<pre><code>User-agent: GPTBot
Disallow: /
User-agent: ChatGPT-User
Disallow: /
</code></pre>
<hr />
<p>Soundtrack for each month:</p>
<ul>
<li>July: <a href="https://sigurros.bandcamp.com/album/tta">Sigur Rós — <abbr>ÁTTA</abbr></a></li>
<li>August: <a href="https://gregoryalanisakov.bandcamp.com/album/appaloosa-bones">Gregory Alan Isakov — Appaloosa Bones</a></li>
<li>September: <a href="https://thenational.bandcamp.com/album/laugh-track">The National — Laugh Track</a>. Honorable mention: Oneohtrix Point Never’s phenomenal track <a href="https://www.youtube.com/watch?v=_kyFqe36BqM">A Barely Lit Path</a>.</li>
</ul>
<hr />
<p>Rest in peace <a href="https://en.wikipedia.org/wiki/Molly_Holzschlag">Molly E. Holzschlag</a> (1963–2023), author, educator and web standards <abbr>OG</abbr>.</p>
WSF 442023-07-02T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-07-02/<h2>News</h2>
<p><strong role="heading" aria-level="3" id="safari-17">Safari 17</strong> will support Progressive Web Apps on the Mac, complete with Web Push and Badging, as presented by Rachel Ginsberg in the <a href="https://developer.apple.com/videos/play/wwdc2023/10120/">What’s new in web apps</a> <abbr class="sc">WWDC23</abbr> video. Thomas Steiner has documented the experience of installing and using <a href="https://blog.tomayac.com/2023/06/07/web-apps-on-macos-sonoma-14-beta/">Web Apps on mac<abbr class="sc">OS</abbr> Sonoma 14 Beta</a>.</p>
<p>For more details, check out the announcement post <a href="https://webkit.org/blog/14205/news-from-wwdc23-webkit-features-in-safari-17-beta/">News from <abbr class="sc">WWDC23</abbr>: WebKit Features in Safari 17 beta</a>. I’ve cherry-picked some features below.</p>
<p>This version ships improvements to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust">the <code>font-size-adjust</code> property</a> which harmonizes the metrics of different fonts. The two-value syntax and the <code>from-font</code> keyword are now supported, along with the related <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/size-adjust"><code>@font-face/size-adjust</code></a> descriptor.</p>
<p>JavaScript Set objects get new methods for <a href="https://github.com/tc39/proposal-set-methods">performing set operations</a>, such as <code>union()</code>, <code>intersection()</code>, and <code>difference()</code>. And <abbr class="sc">URL</abbr>s can now be validated without throwing with <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/canParse_static"><code>URL.canParse()</code></a>.</p>
<p>More significantly, Safari 17 adds support for <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover">the <code>popover</code> attribute</a>, which opens an element above all other elements in the so-called “top layer”.</p>
<p><strong role="heading" aria-level="3">Chrome 114</strong> has also shipped the popover API, the first stable browser to do so.</p>
<p>Una Kravets gives the rundown in <a href="https://developer.chrome.com/blog/introducing-popover-api/">Introducing the popover API</a>. Hidde de Vries explains how to assign the proper <abbr class="sc">ARIA</abbr> roles to popover elements in <a href="https://hidde.blog/popover-semantics/">Semantics and the <code>popover</code> attribute: what to use when?</a>.</p>
<p><strong role="heading" aria-level="3">Web <abbr class="sc">API</abbr> removals</strong>. Mutation events have been deprecated for a good while, but they <a href="https://developer.chrome.com/en/blog/mutation-events-deprecation/">will be removed altogether from Chrome</a> in July 2024, so now is a good time to update or replace any code that still uses them. On a shorter timeframe: <a href="https://developer.chrome.com/blog/migrate-way-from-data-urls-in-svg-use/">Migrate away from data <abbr class="sc">URL</abbr>s in <abbr class="sc">SVG</abbr> <code><use></code> element</a>.</p>
<p><strong><abbr class="sc">MDN</abbr></strong> has introduced <a href="https://developer.mozilla.org/en-US/blog/introducing-the-mdn-playground/">a code playground</a>, which is <q>pretty much what you expect it to be — a simple way to preview <abbr class="sc">HTML</abbr>, <abbr class="sc">CSS</abbr>, and JavaScript.</q></p>
<h2>Articles</h2>
<h3>CSS, new frontiers in</h3>
<p><strong role="heading" aria-level="4">Modern methods.</strong> Get up to speed with <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Modern <abbr class="sc">CSS</abbr> For Dynamic Component-Based Architecture</a> by Stephanie Eckles, based on her talk at <abbr class="sc">CSS</abbr> Day 2023, as well as with Chris Coyier’s whirlwind tour of <a href="https://chriscoyier.net/2023/06/06/modern-css-in-real-life/">Modern <abbr class="sc">CSS</abbr> in Real Life</a>.</p>
<p>Roman Komarov expands on the <a href="https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/">space toggle hack</a> by taking advantage of the fact that custom properties that depend on each other become invalid: <a href="https://kizu.dev/cyclic-toggles/">Cyclic Dependency Space Toggles</a>.</p>
<p><strong role="heading" aria-level="4" id="future-animation">Future animation.</strong> Complex movement that used to be the preserve of JavaScript can now be approximated in <abbr class="sc">CSS</abbr> with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#linear_easing_function"><code>linear()</code> easing function</a>, as explained by Ollie Williams in <a href="https://fullystacked.net/posts/linear/">Using <code>linear()</code> for better animation</a>. The feature has been recently released in Chrome and Firefox, and has positive signals from Safari.</p>
<p>Another nice addition to <abbr class="sc">CSS</abbr> Transitions is the <code>@starting-style</code> declaration that defines the element’s initial styles the moment it’s added to the <abbr class="sc">DOM</abbr> [<a href="https://github.com/w3c/csswg-drafts/issues/8174"><abbr class="sc">CSSWG</abbr>#8174</a>]. You can produce a <abbr class="sc">CSS</abbr>-only insert effect, as demonstrated by Bramus in <a href="https://www.bram.us/2023/05/24/the-yellow-fade-technique-with-modern-css-using-starting-style/">The Yellow Fade Technique with Modern <abbr class="sc">CSS</abbr> using <code>@starting-style</code></a>. The feature has been prototyped in Chrome Canary 115 behind the <em>Experimental Web Platform Features</em> flag.</p>
<p>Then there’s a piece of JavaScript code I’ve been ardently looking forward to deleting in favor of a <abbr class="sc">CSS</abbr> alternative: <a href="https://drafts.csswg.org/scroll-animations-1/">Scroll-driven animations</a> promise to offer a simple, declarative way to bind an animation’s timeline to the scroll position of a container via the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline"><code>animation-timeline</code></a> property. Bramus has created <a href="https://scroll-driven-animations.style/">a gallery of demos</a>, complemented by <a href="https://developer.chrome.com/articles/scroll-driven-animations/">an explainer</a>.</p>
<p>In <a href="https://kizu.dev/scroll-driven-animations/">Future <abbr class="sc">CSS</abbr>: Wishes Granted by Scroll-driven Animations</a>, Roman Komarov explores a few possible use cases, including an easier way to detect the <em>stuck</em> state of sticky elements. In subsequent articles, Roman stretches the feature to achieve <a href="https://kizu.dev/fit-to-width-text/">Fit-to-Width Text</a> and <a href="https://kizu.dev/position-driven-styles/">Position-Driven Styles</a>. Sprinkle some style queries on top and you can also create animations that are triggered by elements becoming visible in their scroll container, as shown by Bramus in <a href="https://www.bram.us/2023/06/15/scroll-triggered-animations/">Creating Scroll-Triggered Animations</a>.</p>
<h3>Web APIs</h3>
<p>The ability to animate page navigations has been one of the reasons designers & developers have opted for JavaScript-driven, or JavaScript-enhanced, websites. Thankfully, as Jeremy Keith shows, you won’t need any of that to <a href="https://adactio.com/journal/20195">add view transitions to your website</a>.</p>
<p>View transitions can also be used to animate interface changes within the same page, as shown by Paul Hebert in <a href="https://cloudfour.com/thinks/animating-slack-reminders-with-the-view-transitions-api/">Animating Slack reminders with the View Transitions <abbr class="sc">API</abbr></a>.</p>
<p>While <a href="https://open-ui.org/components/selectmenu/">the <code><selectmenu></code> element</a> has aspects that still need sorting out before it’s ready to ship, the styling possibilities it opens up are pretty impressive. In <a href="https://www.smashingmagazine.com/2023/06/advanced-form-control-styling-selectmenu-anchoring-api/">Advanced Form Control Styling With Selectmenu And Anchoring <span class="sc">API</span></a>, Brecht De Ruyte will <q>walk through an early implementation of this new experimental element by creating a pattern that you would never have thought possible with <abbr class="sc">CSS</abbr> alone — a radial selection menu.</q></p>
<p><strong role="heading" aria-level="4">The capable web.</strong> Thomas Steiner has co-written a series of articles on how web apps benefited from new <abbr class="sc">API</abbr>s: how the game editor Construct 3 uses the File System Access <abbr class="sc">API</abbr> to <a href="https://developer.chrome.com/en/blog/how-construct3-uses-the-file-system-access-api/">let users save their games</a> with Ashley Gullen, how vector image editing app Boxy <abbr class="sc">SVG</abbr> uses the Local Font Access <abbr class="sc">API</abbr> to <a href="https://developer.chrome.com/en/blog/how-boxysvg-uses-the-local-font-access-api/">let users pick their favorite local fonts</a> with Jarek Foksa, and how Photoshop <a href="https://developer.chrome.com/en/blog/how-photoshop-solved-working-with-files-larger-than-can-fit-into-memory/">solved working with files larger than can fit into memory</a> with Nabeel Al-Shamma.</p>
<p>Felt, makers of a mapmaking web app, have been documenting their switch from <abbr class="sc">SVG</abbr> to <code><canvas></code> to improve performance (<a href="https://felt.com/blog/from-svg-to-canvas-part-1-making-felt-faster">Making Felt faster</a>), a process that entails bringing your own event system (<a href="https://felt.com/blog/svg-to-canvas-part-2-building-interactions">A new way of building interactions</a>), among other things.</p>
<h3>Assorted topics</h3>
<p>Marc Edwards compares Adobe Illustrator’s <a href="https://bjango.com/articles/shapebuildervspathfinder/">Shape builder vs. pathfinder</a>: <q>The pathfinder panel was added to Illustrator in 2001, and the shape builder tool was added in 2010. Given shape builder is newer, many people assume it’s better. They’re both useful, but work in different ways</q>. (Also available <a href="https://www.youtube.com/watch?v=EprkU5El594">in video format</a>)</p>
<p>In <a href="https://blog.varunramesh.net/posts/intro-parser-combinators/">An Introduction to Parser Combinators</a>, Varun Ramesh builds from scratch <q>a command parser inspired by old-school text adventures</q>.</p>
<p>Mitchell Hashimoto’s <a href="https://mitchellh.com/writing/building-large-technical-projects">Approach to Building Large Technical Projects</a> prioritizes demos and using your own software as driving forces against stagnation.</p>
<p>Stephen Coles introduces the collection of 26,000 items of printed ephemera that are to join the Letterform Archive: <a href="https://letterformarchive.org/news/richard-sheaff-vintage-ephemera/">Coming Soon: The Richard Sheaff Ephemera Collection</a>.</p>
<h2>Tools and resources</h2>
<p><a href="https://github.com/WordPress/playground-tools/tree/trunk/packages/wp-now">wp-now</a> streamlines the process of setting up a local WordPress environment. It uses the WordPress Playground, which runs <span class="sc">PHP</span> and <span class="sc">SQL</span>ite inside the browser via <span class="sc">WASM</span>, so it’s really easy to set up and launch from within a theme or plugin folder. See the announcement post: <a href="https://developer.wordpress.com/2023/05/23/wp-now-launch-a-local-environment-in-seconds/">wp-now: Launch a Local Environment in Seconds</a>.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> --save-dev @wp-now/wp-now<br />npx wp-now start</code></pre>
<p><strong>Watch this (design)space.</strong> <a href="https://github.com/googlefonts/fontra">Fontra</a> by Just van Rossum and Black[Foundry] is an open-source, browser-based font editor, currently in development.</p>
<p><a href="https://googlefonts.github.io/gf-guide/">Google Fonts Guide</a> <q>aims to help people navigate the requirements and recommendations for contributing to Google Fonts</q>. It gathers various font engineering tips that are useful even outside <abbr class="sc">GF</abbr> workflows.</p>
<p><a href="https://remix-tools.com/pdf/Tim_Ahrens_MM_method.pdf">A Multiple Master based method for scaling glyphs without changing the stroke characteristics</a> (<abbr class="sc">PDF</abbr>), an essay by Tim Ahrens, originally written in 2006 as part of the <abbr class="sc">MA</abbr> Typeface Design programme at the University of Reading, which goes into the math behind <a href="https://remix-tools.com/">Font Remix Tools</a>.</p>
<p><a id="primarium" href="https://www.youtube.com/watch?v=qi3dnWP1R60">Primarium, A Global View On Handwriting Education</a>, a public lecture with Veronica Burian and José Scaglione of TypeTogether, a snapshot of their ongoing research into how handwriting is taught: <q>The experience of handwriting education in primary schools around the world is not one-size-fits-all; there is great diversity in how handwriting is taught, and approaches to it are constantly evolving</q>. Part of the <a href="https://2023.typographics.com/">Typographics 2023</a> design festival.</p>
<p><a href="https://bvhtype.com/custom/dina-chaumont">Dina Chaumont</a> is a lively typeface designed by baldinger•vu-huu for the city of Chaumont. <q>Intending to make every letter a poster, b•v-h type has designed a monospace display typeface, which fits into this <abbr class="sc">DIN A</abbr> format. This format can be used in portrait or landscape, so two different sign widths coexist.</q> The fonts are offered as a free download.</p>
<p>As part of the <a href="https://xcicero.esad-gv.net/">X Cicéro</a> type design workshop at <abbr class="sc">ÉSAD</abbr> Valence, a series of fonts created by students based on the school’s collection of wooden typefaces are made available under the <abbr class="sc">OFL</abbr> license (via Fonts in Use).</p>
<p><a href="https://typoteka.pl/en">Typoteka</a> is <q>an index of typefaces created by authors associated with Poland</q>.</p>
<hr />
<p><strong>A new game</strong> by the New York Times called <a href="https://www.nytimes.com/games/connections">Connections</a> invites you to group words that share a common thread.</p>
<p><em>Soundtrack:</em> <a href="https://eluvium.bandcamp.com/album/whirring-marvels-in-consensus-reality">Eluvium — (Whirring Marvels In) Consensus Reality</a> is shaping up to be one of my favorite albums of the year.</p>
WSF 432023-05-19T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-05-19/<h2>News</h2>
<p>As Watch/Star/Fork was snoozing away the month of April, browser makers were busy bringing interoperability to several web platform features.</p>
<p><b role="heading" aria-level="3">Firefox 112</b> ships with support for <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inert">the <code>inert</code> <abbr class="sc">HTML</abbr> attribute</a>, which disables interaction with the element and excludes it from the accessibility tree. The attribute is now available across all three major browser engines.</p>
<p>Although announced in the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/112">release notes</a>, and subsequently picked up by <abbr class="sc">MDN</abbr> browser compatibility data, <abbr class="sc">CSS</abbr> exponential functions such as <code>log()</code> and <code>sqrt()</code> don’t seem to have been enabled for this release. (Here’s a handy CodePen by Ana Tudor that checks <a href="https://codepen.io/thebabydino/pen/yLRBJXP">support for math functions</a>)</p>
<p><b role="heading" aria-level="3">Firefox 113</b> catches up on <code>css-color-4</code> and <code>css-color-5</code> functions. The <code>lab()</code>, <code>oklab()</code>, <code>lch()</code>, <code>oklch()</code>, and <code>color()</code> syntaxes, as well as <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix">the <code>color-mix()</code> function</a> for combining colors, are now available across engines.</p>
<p>Another win for interoperability is the inclusion of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API">Compression Streams API</a>, which reduces the complexity of working with compressed data and archives. Read more about <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/113">Firefox 113 for developers</a>.</p>
<p><b role="heading" aria-level="3" id="safari-16-4">Safari 16.4</b> is a <a href="https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes">hefty release</a> with several new features. The <a href="https://drafts.csswg.org/css-color-5/#relative-colors">relative color syntax</a> lets you derive a new color from an existing one:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.overlay</span> <span class="token punctuation">{</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgb</span><span class="token punctuation">(</span>from black r g b / 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Because it allows math operations with <code>calc()</code>, you can do all sorts of cool things, such as inverting an <abbr class="sc">RGB</abbr> color:</p>
<figure>
<pre class="language-css"><code class="language-css"><span class="token selector">.warning</span> <span class="token punctuation">{</span><br /> <span class="token property">--bg</span><span class="token punctuation">:</span> yellow<span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">rgb</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--bg<span class="token punctuation">)</span> <span class="token function">calc</span><span class="token punctuation">(</span>255 - r<span class="token punctuation">)</span> <span class="token function">calc</span><span class="token punctuation">(</span>255 - g<span class="token punctuation">)</span> <span class="token function">calc</span><span class="token punctuation">(</span>255 - b<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<figcaption>
<p>Implementing the <code>invert()</code> filter with the relative color syntax. Note that Safari 16.4 implements a slightly older version of the spec, where the <abbr class="sc">RGB</abbr> components are treated as <code><percentage></code>s, so what actually works right now is <code>calc(100% - r)</code>.</p>
</figcaption>
</figure>
<p>Other <abbr class="sc">CSS</abbr> features include <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin-trim">the <code>margin-trim</code> property</a> and line-height-derived units <a href="https://danburzo.ro/line-height-lh/"><code>lh</code> and <code>rlh</code></a>. Safari also joins Chromium-based browsers in supporting <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property"><code>@property</code></a>, with which you can describe a custom <abbr class="sc">CSS</abbr> property more precisely by defining its syntax, default value, and whether it is inherited.</p>
<p>Their addition to Safari 16.4 makes JavaScript import maps <a href="https://web.dev/import-maps-in-all-modern-browsers/">supported cross-browser</a>.</p>
<p>JavaScript arrays get two new methods, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/group"><code>group()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/group"><code>groupToMap()</code></a>. These were easy enough to implement, but it’s nice to see them built into the standard library.</p>
<p>For other big-ticket features, see <a href="https://webkit.org/blog/13966/webkit-features-in-safari-16-4/">WebKit Features in Safari 16.4</a>.</p>
<p><b role="heading" aria-level="3">Safari 16.5</b> adds support for the <abbr class="sc">CSS</abbr> nesting syntax, first made available in a stable browser <a href="https://developer.chrome.com/en/blog/new-in-chrome-112/">with Chrome 112</a>.</p>
<p><b role="heading" aria-level="3" id="baseline">A line in the sand.</b> Rachel Andrews <a href="https://web.dev/introducing-baseline/">introduces Baseline</a>, an initiative by Google to mark whether web platform features are <q>safe to use</q> from the point of view of interoperability. The label will be rolled out in web.dev articles, <a href="https://developer.mozilla.org/en-US/blog/baseline-unified-view-stable-web-features/">across <abbr class="sc">MDN</abbr> pages</a>, and more. <q>Features become part of Baseline when they are supported in the current and previous version of all major browsers—Chrome, Edge, Firefox, and Safari.</q></p>
<p>The label seeks to address the very real struggle of keeping up with web platform features in the age of mostly-self-updating browsers. However, as Mathias Schäfer points out in <a href="https://molily.de/browser-compatibility-baseline/">On browser compatibility and support baselines</a>, drawing the line at two-month-old browsers is a rather optimistic definition of “safe to use”.</p>
<h2>Articles</h2>
<h3>CSS</h3>
<p><a href="https://kizu.dev/anchor-positioning-experiments/">Future <abbr class="sc">CSS</abbr>: Anchor Positioning</a>, an in-depth exploration with practical use-cases by Roman Komarov. The <code>anchor-name</code> property and <code>anchor()</code> function are currently available in Chome Canary.</p>
<p>Peter-Paul Koch’s <a href="https://quirksmode.org/css/flexbox-algorithm.html">Simplified flexbox algorithm</a> <q>is enough to understand about 95% of practical use cases</q>.</p>
<p><a href="https://moderncss.dev/container-query-units-and-fluid-typography/">Container Query Units and Fluid Typography</a>, a tutorial by Stephanie Eckles on scaling type based on container size.</p>
<p>In <a href="https://keithjgrant.com/posts/2023/04/scoped-css-is-back/">Scoped <abbr class="sc">CSS</abbr> is back</a>, Keith J. Grant explains how <a href="https://drafts.csswg.org/css-cascade-6/#scoped-styles"><code>@scope</code> declarations</a> work. While <abbr class="sc">CSS</abbr> nesting (with which scoped styles share some use-cases) is simply syntax sugar for repeated selectors, <code>@scope</code> <a href="https://css.oddbird.net/scope/nesting/#do-we-really-need-both-scoping-and-nesting">imposes DOM boundaries</a> on matching elements.</p>
<p><b role="heading" aria-level="3"><abbr class="sc">CSS</abbr> proposals.</b> Rachel Andrews invites feedback on <a href="https://developer.chrome.com/blog/reading-order/">Solving the <abbr class="sc">CSS</abbr> layout and source order disconnect</a> with the <code>reading-order</code> and <code>reading-order-items</code> properties.</p>
<p>With <a href="https://developer.chrome.com/en/blog/css-text-wrap-balance/"><code>text-wrap: balance;</code></a> whetting the appetite for <a href="https://en.wikipedia.org/wiki/Microtypography">microtypography</a>, there’s now renewed interest in expressing with <abbr class="sc">CSS</abbr> the preference against exceedingly short final lines in paragraphs. The fun slash challenging part is figuring out its place in the default (greedy) line-breaking algorithm, as well as the role it plays in more sophisticated line-balancing alternatives. In the <a href="https://github.com/w3c/csswg-drafts/issues/3473"><abbr class="sc">CSSWG</abbr> discussion</a>, Amelia Bellamy-Royds proposes the <code>min-last-line</code> property. Richard Rutter riffs on this idea in <a href="https://clagnut.com/blog/2425/">Preventing too-short final lines of text blocks</a>.</p>
<h3>JavaScript, and lack thereof</h3>
<p><a href="https://ryanmulligan.dev/blog/sticky-header-scroll-shadow/">Sticky Page Header Shadow on Scroll</a>, a short tutorial by Ryan Mulligan on using <code>IntersectionObserver</code> to detect when the page is scrolled.</p>
<p>Thomas Steiner on the <a href="https://developer.chrome.com/articles/origin-private-file-system/">origin-private file system</a>, a mechanism for web pages to read and write to an in-browser file system optimized for performance.</p>
<p><a href="https://lea.verou.me/2023/04/private-fields-considered-harmful/"><abbr class="sc">JS</abbr> private class fields considered harmful</a>, warns Lea Verou, due to their incompability with proxies: <q>As a library author, I’ve decided to avoid private class fields from now on and gradually refactor them out of my existing libraries.</q></p>
<p>A rule of thumb for progressive enhancement, especially useful at the beginning of a project: <q>Your app should work in a read-only mode without JavaScript</q>, says Jeremy Keith in <a href="https://adactio.com/journal/20113">Read-only web apps</a>.</p>
<h3>Accessibility</h3>
<p>Scott O’Hara makes the case that <a href="https://www.scottohara.me/blog/2023/03/21/visually-hidden-hack.html">Visually hidden content is a hack that needs to be resolved, not enshrined</a>.</p>
<p>Two new entries in the Foundations series on the Tetralogical blog: Graeme Coleman describes the <a href="https://tetralogical.com/blog/2023/03/17/foundations-pointer-gestures/">different forms of pointer gestures</a>, as well as <q>how and when to provide alternatives for people who can’t perform them</q>. Henny Swan explains the <code>aria-label</code>, <code>aria-labeledby</code>, and <code>aria-describedby</code> <abbr class="sc">HTML</abbr> attributes for providing <a href="https://tetralogical.com/blog/2023/04/05/accessible-names-and-descriptions/">accessible names and descriptions</a>.</p>
<p>This year’s <a href="https://webaim.org/projects/million/">WebAIM Million report</a> does not show much progress on the accesibility front, even on those issues that can be automatically detected and easily fixed. Hidde de Vries shows how these most frequent problems can be sorted out: <a href="https://hidde.blog/common-a11y-issues/">Common accessibility issues that you can fix today</a> and <a href="https://hidde.blog/more-common-a11y-issues/">More common accessibility issues that you can fix today</a>. (Via Eric Eggert, who reiterates that <a href="https://yatil.net/blog/accessibility-action">We need accessibility action — Now!</a>)</p>
<h3>Graphics</h3>
<p><a href="https://raphlinus.github.io/curves/2023/04/18/bezpath-simplify.html">Simplifying Bézier paths</a> by Raph Levien: <q>The techniques in this post, and code in <a href="https://github.com/linebender/kurbo">kurbo</a>, come close to producing the globally optimum Bézier path to approximate the source curve, with fairly decent performance (as well as a faster option that’s not always minimal in the number of segments)</q>.</p>
<p>For her generative art project <a href="https://charlottedann.com/project/ceramics">Ceramics</a>, Charlotte Dann has been releasing technical deep-dives on <a href="https://charlottedann.com/article/ceramics-2d-to-3d">Creating carved surfaces</a>, <a href="https://charlottedann.com/article/magical-vector-fields">Magical vector fields</a>, <a href="https://charlottedann.com/article/realistic-ceramic-materials">Realistic ceramic materials</a>, and <a href="https://charlottedann.com/article/handwriting-in-canvas">Handwriting with the Canvas API</a>.</p>
<h2>Tools and resources</h2>
<p><b role="heading" aria-level="3">Reading types.</b> An offshoot of <a href="http://rosaliewagner.com/">Rosalie Wagner</a>’s research project investigating the role of typography in teaching children to read and write simultaneously, <a href="https://github.com/RosaWagner/Borel">Borel</a> is a charming cursive typeface, released under the Open Font License.</p>
<p>Relatedly: <a href="https://www.schulschrift.at/index_en.html">Prima</a>, <q>a typeface family that has been designed to help in learning to read and write</q> by Titus Nemeth and Martin Tiefenthaler, has been <a href="https://github.com/WienerSchriften/Prima">released on GitHub</a> under the <abbr class="sc">CC BY-NC-SA 4.0</abbr> license.</p>
<p><a href="https://github.com/intel/intel-one-mono/tree/main">Intel One Mono</a> is <q>an expressive monospaced font family that’s built with clarity, legibility, and the needs of developers in mind</q>, designed by Fred Shallcrass at Frere-Jones Type. <q>A panel of low-vision and legally blind developers provided feedback at each stage of design.</q></p>
<p><a href="https://legible-typography.com/en/">Legibility: How and why typography affects ease of reading</a> has been released as a digital book. <q>Mary Dyson spent most of her academic life at the renowned Department of Typography & Graphic Communication at the University of Reading (UK). She has dedicated her career to research into reading and typography, writing numerous papers on the subject. In front of you is a digital version of her comprehensive introduction to legibility. It updates and extends existing books summarising contemporary legibility research in an accessible form.</q></p>
<p><a href="https://letterformarchive.org/news/books-about-signs/">For Your Reference: Books About Signs</a> by Tanya George at the Letterform Archive: <q>From retail branding to wayfinding, sign letters shape our urban landscape. Get a peek at the Archive’s stacks in this first stop on our reference library tour.</q></p>
<p>Nicholas Rougeux has created a digital version of John Earhart’s <a href="https://www.c82.net/color-printer/">The Color Printer: A Treatise on the Use of Colors in Typographic Printing</a> (1892). As we’ve come to expect, there’s also <a href="https://www.c82.net/blog/?id=92">a making-of</a>.</p>
<p><a href="https://github.com/ColonelParrot/jscanify">jscanify</a> is an open-source JavaScript mobile document scanner that can detect page contours and correct their perspective. <a href="https://github.com/freedmand/textra">textra</a> is a macOS command-line application that extracts text from images, <abbr class="sc">PDF</abbr>s, and audio files, using native <abbr class="sc">API</abbr>s (via <a href="https://simonwillison.net/2023/Mar/23/textra/">Simon Willison</a>).</p>
<p><a href="https://observablehq.com/plot/">Observable Plot</a>, <q>the JavaScript library for exploratory data visualization</q> powered by <span class="sc">D3</span>.</p>
<p><a href="https://www.usethehumanvoice.com/formats/">Blog post formats for teams</a>, assembled by Giles Turnbull: <q>It might be helpful for teams who want to use a blog to work in the open, but aren’t sure what good blogging looks like.</q></p>
<p><a href="https://frankrausch.com/ios-navigation">Modern iOS Navigation Patterns</a> catalogued by Frank Rausch.</p>
<hr />
<p><strong>Type city.</strong> <a href="https://www.productiontype.com/news/the_paris_typography_map">Paris Typography Map</a>, prepared by Production Type for the <a href="https://atypi.org/conferences-events/atypi-paris-2023/">ATypI 2023</a> conference.</p>
<p><strong>Bread, all about it.</strong> <a href="https://github.com/hendricius/the-sourdough-framework">The Sourdough Framework</a> is an <q>open source book dedicated to helping you to make the best possible sourdough bread at home</q>.</p>
<p><strong>On heavy rotation:</strong> <a href="https://feverray.bandcamp.com/album/radical-romantics">Radical Romantics</a> by Fever Ray, <a href="https://elmichelsaffair.bandcamp.com/album/glorious-game">Glorious Game</a> by El Michels Affair & Black Thought, <a href="https://timhecker.bandcamp.com/album/no-highs">No Highs</a> by Tim Hecker. Also warming up to the <a href="https://thenational.bandcamp.com/album/first-two-pages-of-frankenstein">latest National album</a>.</p>
wsf-xlii2023-03-13T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-03-13/<h2>News</h2>
<p><strong>Firefox 111</strong>, out tomorrow, adds support for the remaining <code>css-color-4</code> color spaces: <code>lab()</code>, <code>lch()</code>, <code>oklab()</code> and <code>oklch()</code>, as well as the <code>color()</code> syntax for the entire set of predefined spaces. As per the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/111">release notes</a>, this feature is behind the <code>layout.<wbr />css.<wbr />more_color_4.<wbr />enabled</code> flag.</p>
<p>The release also includes support for the <a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API#origin_private_file_system">origin-private file system</a>.</p>
<p><strong>Chrome 111.</strong> In addition to the <abbr>CSS</abbr> color features mentioned in a <a href="https://danburzo.ro/watchstarfork/2023-02-07/">previous issue</a>, the release includes initial support for <a href="https://drafts.csswg.org/css-view-transitions-1/"><abbr>CSS</abbr> View Transitions</a>, as <a href="https://developer.chrome.com/en/blog/spa-view-transitions-land/">explained by Jake Archibald</a>.</p>
<p>With this Chrome release, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Functions#trigonometric_functions">trigonometric functions in <abbr>CSS</abbr></a> are now supported across major browser engines. Practice them by <a href="https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/">creating a clock with the new <abbr>CSS</abbr> <code>sin()</code> and <code>cos()</code> trigonometry functions</a> with Mads Stoumann.</p>
<p><strong>Chrome 113</strong>, currently Canary, ships behind the <em>Experimental Web Platform features</em> flag support for <a href="https://drafts.csswg.org/css-text-4/#text-wrap"><code>text-wrap: balance</code></a>, which wraps text into lines by taking into account the entire element content, to produce lines of similar lengths. It’s meant for small runs of text, such as headings or captions, where the effect is desirable.</p>
<p>The exact algorithm for line balancing is left for browsers to decide. Because it’s expected to have greater time complexity than usual (greedy) wrapping, browsers are allowed to bail out of line balancing for texts longer than ten lines. A separate value, <code>text-wrap: pretty</code>, is meant for opting into more sophisticated line-breaking for long texts. Together, these values may spell, as Richard Rutter observes, <a href="https://clagnut.com/blog/2424/">an end to typographic widows on the web</a>.</p>
<p>Still behind the <em>Experimental Web Platform features</em> flag in Chrome Canary 113 you’ll find <a href="https://drafts.csswg.org/css-anchor-position-1/">anchor positioning</a>, which lets you stick elements next to other elements. Jhey Tompkins explains in <a href="https://developer.chrome.com/en/blog/tether-elements-to-each-other-with-css-anchor-positioning/">Tether elements to each other with <abbr>CSS</abbr> anchor positioning</a>.</p>
<p><strong>Deno 1.31</strong> <a href="https://deno.com/blog/v1.31">has been released</a> with support for <code>package.json</code>, and other improvements to Node and npm compatibility that simplify migration to the new runtime.</p>
<h2>Articles</h2>
<h3>CSS</h3>
<p>Develop an intuition on <a href="https://www.joshwcomeau.com/css/rules-of-margin-collapse/">The Rules of Margin Collapse</a> with interactive illustrations by Josh W. Comeau. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin-trim"><code>margin-trim</code></a> property, available in the upcoming Safari 16.4, can remove margins where elements meet their container, so that’s one collapse scenario fewer to think about.</p>
<p>Šime Vidas with <a href="https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/">Everything You Need to Know About the Gap After the List Marker</a>, on working with the limited set of styles accepted by the <code>::marker</code> pseudo-elememt.</p>
<p><a href="https://set.studio/some-simple-ways-to-make-content-look-good/">Some simple ways to make content look good</a>, a primer on styling long-form text by Andy Bell.</p>
<h3>Accessibility</h3>
<p>In <a href="https://hidde.blog/ideal-a11y-guidance/">My ideal accessible components resource is holistic, well tested and easy to use</a>, Hidde de Vries outlines what to aim for in materials that teach people how to build the accessible web-beyond-documents. <q>The gist of it is: make it easier for people to get accessibility right. And the opposite, too: make it harder to get it wrong.</q> The article points to several great resources.</p>
<p>Speaking of which, <a href="https://www.tpgi.com/evolving-custom-sliders/">Evolving custom sliders</a> by James Edwards <q>demonstrates a hybrid technique for creating custom sliders, combining the accessibility and usability benefits of a native range input, with the markup and design flexibility of a pure custom slider</q>.</p>
<h3>JavaScript</h3>
<p><a href="https://www.redblobgames.com/making-of/draggable/">Draggable objects</a>, a great deep-dive by Amit Patel on combining DOM mouse and touch events to move things on the screen.</p>
<p>With the occasion of Firefox 108 having shipped support for <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap"><code><script type=importmap></code></a>, Yoshi Huang with a two-part explainer of JavaScript Import maps: the <a href="https://spidermonkey.dev/blog/2023/02/23/javascript-import-maps-part-1-introduction.html">Introduction</a>, followed by an <a href="https://spidermonkey.dev/blog/2023/03/02/javascript-import-maps-part-2-in-depth-exploration.html">In-Depth Exploration</a>.</p>
<h3>Making software</h3>
<p><a href="https://www.inkandswitch.com/upwelling/">Upwelling: Combining real-time collaboration with version control for writers</a>, a new Ink & Switch prototype and essay by Karissa Rae McKelvey, Scott Jenson, Eileen Wagner, Blaine Cook, and Martin Kleppmann. <q>In the Upwelling project we have built an experimental editor that aims to satisfy the needs of professional writers and editors. It allows co-authors to collaborate in real time when they wish to, but it also supports work on private drafts that can be shared and merged only when their authors are ready. By combining elements of real-time collaboration with ideas from version control systems, Upwelling supports writers in maintaining their creative privacy and editors in ensuring accurate results.</q></p>
<p><a href="https://grantshandy.github.io/posts/raycasting/">Write a First Person Game in 2KB With Rust</a> with Grant Handy: <q>My goal here is to show how something that looks complicated can be broken down into simple pieces, and if I’ve done my job right, it should feel like you’ve discovered how the game works.</q></p>
<h3>Reading, writing, process</h3>
<p><a href="https://ericwbailey.website/published/i-doubled-down-on-rss/">I doubled-down on <abbr>RSS</abbr></a>: Eric Bailey meditates on the experience of going through his Twitter network to subscribe to people’s blogs. It’s peppered with bits of insight ranging from tips on providing and surfacing feeds, to the effort of explaining the quirks of <abbr>RSS</abbr> when trying to get people on board, and more.</p>
<p><a href="https://boffosocko.com/2023/03/09/the-memindex-method-an-early-precursor-of-the-memex-hipster-pda-43-folders-gtd-basb-and-bullet-journal-systems/">The Memindex Method</a>, Chris Aldrich on <q>an early precursor of the Memex, Hipster <abbr>PDA</abbr>, 43 Folders, <abbr>GTD</abbr>, BaSB, and Bullet Journal systems</q> from 1906 (via <abbr>SM</abbr>).</p>
<p><a href="https://letterformarchive.org/news/digitizing-objects-of-lettering-typography-and-graphic-design/">From Paper to Screen: The Digital Capture of Lettering, Typography, Printmaking, and Graphic Design</a>, a glimpse at the tools and process by April Harper, digitization librarian at the Letterform Archive.</p>
<h2>Tools and resources</h2>
<p><a href="https://shantellsans.com/">Shantell Sans</a>, an open-source, marker-style font based on the <q>artwork, handwriting, and creative philosophy</q> of artist Shantell Martin, designed in collaboration with Stephen Nixon of <a href="https://arrowtype.com/">Arrow Type</a>. This is the type design studio’s second open-source release, after <a href="https://www.recursive.design/">Recursive</a>.</p>
<p><a href="https://learn.microsoft.com/en-us/shows/pwa-for-beginners/"><abbr>PWA</abbr> for Beginners</a>, a 17-chapter video series by Beth Pan and her team at Microsoft that <q>walks you through building your own Progressive Web Apps that can run cross platforms and combine the best of web and native features</q>. Also available <a href="https://www.youtube.com/playlist?list=PLlrxD0HtieHjqO1pNqScMngrV7oFro-TY">as a YouTube playlist</a>.</p>
<p><a href="https://themeshaper.com/">ThemeShaper</a>, a blog by the Automattic Theme Team where <q>we publish experiments, write about web design, WordPress themes, show-off our custom in-house themes, share team news and announcements, and explain what we’re all about</q>. New to me, despite being published since, erm… 2008? (via <a href="https://css-tricks.com/managing-fonts-in-wordpress-block-themes/">Ganesh Dahal</a>)</p>
<h2>Today I learned</h2>
<p>In <a href="https://benmyers.dev/blog/native-visually-hidden/">The Web Needs a Native <code>.visually-hidden</code></a>, Ben Myers provides a few ideas on how to express that more semantically in <abbr>HMTL</abbr> or, ideally, <abbr>CSS</abbr>.</p>
<p>I wanted to understand if there really is a need for another <code>display</code> or <code>visibility</code> value or a new property altogether. Wasn’t there something already specified that could work just as well? I started from <a href="https://github.com/w3c/csswg-drafts/issues/560">the discussion</a> opened by Sara Soueidan.</p>
<p>At the <abbr>ARIA</abbr> level, there is technically <a href="https://w3c.github.io/aria/#aria-hidden"><code>aria-hidden="false"</code></a>, which would cause the element to be <q>exposed to the accessibility <abbr>API</abbr> as if it was rendered.</q> Beyond objections in principle, the biggest practical hurdle with that seems to be that authors <a href="https://github.com/w3c/aria/issues/1256#issuecomment-673603683">have misused it</a> beyond the point of salvageable.</p>
<p>At the <abbr>CSS</abbr> level, <a href="https://www.w3.org/TR/css-speech/#speaking-props-speak"><code>speak: always;</code></a> would override the effect of <code>display: none;</code> and <code>visibility: hidden;</code> and <q>can result in the element being rendered in the aural dimension even though it would not be rendered on the visual canvas</q>. Although I don’t have a mental model of how that <a href="https://github.com/w3c/csswg-drafts/issues/6515">would work out in practice</a>, it remains a plausible mechanism.</p>
<p>Finally, there’s the idea that the technique is overused to the detriment of more appropriate solutions, which raises the question of whether a dedicated web platform feature would be a net positive addition [<a href="https://github.com/whatwg/html/issues/4623">whatwg/html#4623</a>].</p>
<hr />
<p><strong>On slack.</strong> Jenny Odell's new book, <a href="https://www.penguinrandomhouse.com/books/672377/saving-time-by-jenny-odell/">Saving Time: Discovering a Life Beyond the Clock</a>, is out this month.</p>
<p><em>Soundtrack:</em> <a href="https://constantsmiles.bandcamp.com/album/kenneth-anger">Constant Smiles — Kenneth Anger</a></p>
wsf-xli2023-02-26T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-02-26/<h2>News</h2>
<p><strong>Web Push.</strong> One of the standout features in Safari 16.4, now in beta, is its <a href="https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/">support for push notifications</a> on iOS and iPadOS, along with other APIs useful for Progressive Web App developers. The Web Push feature will only be available once the user adds the website to their Home Screen, an action that's currently <a href="https://adactio.com/journal/19911">not very discoverable</a>.</p>
<p><strong>Style goals</strong>. I love the concept of celebrating CSS features when they become available across major browser engines. To that end, there's a <a href="https://web.dev/tags/newly-interoperable/"><em>newly interoperable</em></a> tag on web.dev to which you can subscribe via RSS.</p>
<h2>Articles</h2>
<p><a href="https://joshcollinsworth.com/blog/great-transitions">Easing curves, and better CSS transitions and animations</a>, a guide to <code>cubic-bezier()</code> timing functions by Josh Collinsworth.</p>
<p><a href="https://html5accessibility.com/stuff/2023/02/19/representation-of-style/">Representation of style</a> Steve Faulkner: <q>although the HTML specification attempts to make a distinction between <code><b></code> & <code><strong></code>, and <code><i></code> & <code><em></code>; the general fuzz and fudge of their specification, historical and current use, UI implementations & style representations, coalesce to make attempts to distinguish them futile</q>. <span class="sc">BIUS</span> (Bold, Italic, Underline, Strikethrough) Toolbars and Markdown syntax are just two examples of how ubiquitous user interfaces stand in the way of establishing a distinction in these elements. In hindsight, them defaulting to <code><em></code> and <code><strong></code> probably did not help the cause.</p>
<p>Adrian Roselli documents a set of issues screen readers have with <code><table></code> headers which span multiple rows and columns, and concludes: <a href="https://adrianroselli.com/2023/02/avoid-spanning-table-headers.html">Avoid Spanning Table Headers</a>.</p>
<p><a href="https://alice.pages.igalia.com/blog/how-shadow-dom-and-accessibility-are-in-conflict/">How Shadow DOM and accessibility are in conflict</a> by Alice Boxhall: <q>Shadow DOM encapsulation essentially makes children of a shadow root "private" to any siblings or ancestors of the shadow host. This means that any HTML feature which creates a relationship between elements can't work when a relationship needs to be expressed between an element within a shadow root and one outside of it.</q></p>
<p>In <a href="https://johan.hal.se/wrote/2023/02/17/what-to-expect-from-your-framework/">What to expect from your framework</a>, Johan Halse reminds us there's much more to a web application than rendering libraries.</p>
<p><a href="https://emnudge.dev/blog/react-hostage">React Is Holding Me Hostage</a>, a report from the trenches on the ergonomic limitations of React's reactivity model (managing forms, sharing state), and how that model is hard to optimize for. The article ends with praise for <em>signals</em>, a form of fine-grained reactivity that the new generation of libraries has embraced.</p>
<p><a href="https://mattfrisbie.substack.com/p/spy-chrome-extension">Let's build a Chrome extension that steals everything</a>, a lighthearted but sobering read from Matt Frisbie. <q>Manifest v3 may have taken some of the juice out of browser extensions, but I think there is still plenty left in the tank. To prove it, let’s build a Chrome extension that steals as much data as possible. I’m talking kitchen sink, whole enchilada, Grinch-plundering-Whoville levels of data theft.</q></p>
<p>Oscar de Lama on developing a RAW photo file 'by hand' in a fascinating two-part article. Here's the <a href="https://www.odelama.com/photo/Developing-a-RAW-Photo-by-hand/">first part</a> and <a href="https://www.odelama.com/photo/Developing-a-RAW-Photo-by-hand/Developing-a-RAW-Photo-by-hand_Part-2/">the followup</a>.</p>
<p>In <a href="https://chriscoyier.net/2023/02/16/noodling-on-wordpress-in-2023/">Noodling on WordPress in 2023</a>, Chris Coyier notes the vibe shift in WordPress development precipitated by the Gutenberg Block Editor seeping into every aspect of site-building. In the past few years I've found my local maximum with <a href="https://github.com/danburzo/lathe">Timber + ACF</a>, but I'm keen to find out how much of a mental leap there is to <em>block themes</em>.</p>
<h2>Tools and resources</h2>
<p><em>iOS Access for All</em>, Shelly Brisbin's book on using accessibility features across Apple mobile devices, has been <a href="https://www.iosaccessbook.com/ios-16-edition-released/">updated for iOS 16</a>.</p>
<p>Web.dev's expert-written courses have been enriched with <a href="https://web.dev/learn/privacy/">Learn Privacy</a> by Stuart Langridge and the second half of <a href="https://web.dev/learn/html/">Learn HTML</a> by Estelle Weyl.</p>
<p>In <a href="https://www.kschaul.com/post/2023/02/16/how-the-post-is-replacing-mapbox-with-open-source-solutions/">How The Post is replacing Mapbox with open source solutions</a>, Kevin Schaul assembles a toolbox for making interactive maps for Washington Post:</p>
<ul>
<li><a href="https://openmaptiles.org/">OpenMapTiles</a> for building tiles;</li>
<li><a href="https://maputnik.github.io/">Maputnik</a> for style editing;</li>
<li><a href="https://github.com/protomaps/PMTiles">PMTiles</a> for tile hosting;</li>
<li><a href="https://maplibre.org/projects/maplibre-gl-js/">Maplibre GL JS</a> for client-side rendering.</li>
</ul>
<p><a href="https://meodai.github.io/poline/">Poline</a> by David Aerne is <q>is an enigmatic color palette generator, that harnesses the mystical witchcraft of polar coordinates. Its methodology, defying conventional color science, is steeped in the esoteric knowledge of the early 20th century. This magical technology defies explanation, drawing lines between anchors to produce visually striking and otherworldly palettes. It is an indispensable tool for the modern generative sorcerer, and a delight for the eye.</q></p>
<p><a href="https://github.com/nucliweb/webperf-snippets">webperf-snippets</a> is a <q>curated list of snippets to get Web Performance metrics to use in the browser console</q> by Joan León.</p>
<p><a href="https://github.com/atkirtland/awesome-computational-geometry">awesome-computational-geometry</a> by Aaron Kirtland is <q>a curated list of awesome computational geometry visualizations, frameworks, and resources</q>.</p>
<p><a href="https://github.com/google/swissgl">google/swissgl</a> by Alexander Mordvintsev is a wrapper on top of the WebGL 2 JavaScript API that's <q>designed to reduce the amount of boilerplate code required to manage GLSL shaders, textures and framebuffers when making procedural visualizations or simulations</q>.</p>
<h2>Quick tips</h2>
<p><strong>Find unused JavaScript files.</strong> If you're using <a href="https://esbuild.github.io/">esbuild</a> to bundle your JavaScript project, the <a href="https://esbuild.github.io/api/#metafile">JSON metafile</a> it can produce along the build is very useful. For example, you can list the individual files that were bundled:</p>
<pre class="language-bash"><code class="language-bash">jq <span class="token string">'.inputs | keys | .[]'</span> dist/meta.json <span class="token parameter variable">-r</span> <span class="token operator">|</span> <span class="token function">sort</span></code></pre>
<p>Comparing that to the list of all JavaScript files in the <code>src/</code> folder, we can obtain a list of unimported JavaScript files:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">join</span> <span class="token parameter variable">-v</span> <span class="token number">1</span> <span class="token punctuation">\</span><br /> <span class="token operator"><</span><span class="token punctuation">(</span><span class="token function">find</span> src <span class="token parameter variable">-name</span> <span class="token string">'*.js'</span> <span class="token operator">|</span> <span class="token function">sort</span><span class="token punctuation">)</span> <span class="token punctuation">\</span><br /> <span class="token operator"><</span><span class="token punctuation">(</span>jq <span class="token string">'.inputs | keys | .[]'</span> dist/meta.json <span class="token parameter variable">-r</span> <span class="token operator">|</span> <span class="token function">sort</span><span class="token punctuation">)</span></code></pre>
<p><strong>Matching against Regular Expressions.</strong> For a new <a href="https://culorijs.org/">Culori</a> release I rewrote the color parsing code to make it more robust than the previous regex-only solution. The new parser checks the input character by character, doing things like matching digits, which can be expressed in (at least) two styles:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>ch<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\d</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* it's a digit */</span> <span class="token punctuation">}</span><br /><br /><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\d</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* it's a digit */</span> <span class="token punctuation">}</span></code></pre>
<p>Instinctively I tend to write <em>if string matches pattern</em> without giving it a second thought, because it just reads more naturally. It was only while benchmarking and looking for opportunities to optimize that I realized <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test"><code>RegExp.prototype.test</code></a> is much faster because it does less work. The former needs to return an Array of matches, while the latter returns a simple Boolean. In performance-sensitive contexts, where the operation is performed several times, the difference is significant.</p>
<hr />
<p><em>Soundtrack:</em> <a href="https://www.youtube.com/watch?v=iIyrLRixMs8">Depeche Mode — Ghosts Again</a></p>
wsf-xl2023-02-16T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-02-16/<h2>News</h2>
<p><strong>Firefox 110</strong> has been released with support for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries">Container Queries</a>, a monumental feature which has made its way into <a href="https://web.dev/cq-stable/">all major browser engines</a> in a remarkably short time. See the <a href="https://www.mozilla.org/en-US/firefox/110.0/releasenotes/">release notes</a> for more.</p>
<p>A surprising, niche feature in this new release is the ability to use <a href="https://w3c.github.io/csswg-drafts/css-page/#using-named-pages"><span class="sc">CSS</span> named pages</a> for specifying page breaks for printing. A much appreciated signal of interest in Print <span class="sc">CSS</span>. Fingers crossed for more! Which reminds me I should probably look into <a href="https://github.com/danburzo/percollate/issues/147">adding Firefox support</a> to Percollate.</p>
<p>Still on the topic of Print <span class="sc">CSS</span>, seems that Chrome versions 108 and later print pages using a new layout engine. Morten Stenshorne with a deep-dive on <a href="https://developer.chrome.com/articles/renderingng-fragmentation/">LayoutNG block fragmentation</a> (via <a href="https://csslayout.news/issue-339/">Rachel Andrew</a>).</p>
<p><strong>Safari TP 163.</strong> The standout feature of <a href="https://webkit.org/blog/13839/release-notes-for-safari-technology-preview-163/">this Safari release</a> is the support for <a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid"><span class="sc">CSS</span> masonry layout</a>. The feature has been available for a couple of years in Firefox, under a feature flag.</p>
<p>Also included is a fix for <a href="https://danburzo.ro/css-overscroll-behavior/">overscroll behavior</a>, specifically that <code>overscroll-behavior: none</code> should be honored on documents that are shorter than the viewport, and thus don't elicit a scrollbar. This will reliably disable the <a href="https://danburzo.ro/on-safari-15/#pull-to-refresh">pull-to-refresh behavior</a> introduced in mobile Safari 15:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token property">overscroll-behavior-y</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The <code>css-color-4</code> specification has been adjusted to make parsing <code>color()</code> values stricter: color components can't be omitted for implicit <code>0</code> values. With Safari 15 having already shipped with the more permissive syntax, as long as Safari adjusts its parsing before the next stable release [<a href="https://bugs.webkit.org/show_bug.cgi?id=251152">WebKit#251152</a>], we're left with a pretty neat feature sniff for <em>unpreventable pull-to-refresh</em>:</p>
<figure>
<pre class="language-js"><code class="language-js"><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">'(color: color(xyz))'</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token operator">!</span>window<span class="token punctuation">.</span>TouchEvent</code></pre>
<figcaption>
<p>A short feature sniff to detect the unpreventable pull-to-refresh gesture in mobile Safari. In case the old <code>color()</code> parsing persists to Safari 16.4, you can throw into the mix any CSS property that's been introduced since Safari 16.3, such as <code>CSS.supports('margin-trim: none')</code>.</p>
</figcaption>
</figure>
<p><strong>Catching up</strong>. Lots of explainer articles on the WebKit blog recently for features introduced in previous Safari TP releases: Ryosuke Niwa puts <a href="https://webkit.org/blog/13851/declarative-shadow-dom/">Declarative Shadow <span class="sc">DOM</span></a> in context, Jen Simmons invites us to <a href="https://webkit.org/blog/13813/try-css-nesting-today-in-safari-technology-preview/">try out <span class="sc">CSS</span> Nesting</a>, and Marcos Caceres presents the concept of <a href="https://webkit.org/blog/13862/the-user-activation-api/">User Activation</a>.</p>
<h2>Articles</h2>
<h3>Accessibility</h3>
<p><a href="https://tetralogical.com/blog/2023/02/10/foundations-wai-aria/">Foundations: introduction to <span class="sc">WAI-ARIA</span></a>, a distillation by Henny Swan.</p>
<p>The <span class="sc">WCAG 2.2</span> draft proposes <a href="https://www.w3.org/TR/WCAG22/#focus-appearance">Success Criterion 2.4.11 Focus Appearance</a>, which gives rules for to assessing whether a focus indicator is sufficiently visible. It's a bit hard to parse, especially this phrase:</p>
<blockquote>
<p>is at least as large as the area of a 1 CSS pixel thick perimeter of the unfocused component or sub-component, or is at least as large as a 4 CSS pixel thick line along the shortest side of the minimum bounding box of the unfocused component or sub-component</p>
</blockquote>
<p>In <a href="https://alastairc.uk/2023/02/focus-appearance-thoughts/">Focus Appearance thoughts</a>, Alastair Campbell offers some clarifications.</p>
<p>In this context, <em>area</em> means <em>painted pixel count</em>, and not a surface area or a particular shape. For a rectangle of <code>100×25px</code>, a 1 <span class="sc">CSS</span> pixel thick perimeter has a <mark class="wavy">painted pixel count</mark> of <code>2 * width + 2 * height = 250px</code>. A 4 <span class="sc">CSS</span> pixel thick line along the shortest side has a <mark class="wavy">painted pixel count</mark> of <code>4 * height = 100px</code>.</p>
<h3>CSS</h3>
<p><a href="https://ryanmulligan.dev/blog/grid-gap/"><span class="sc">CSS</span> Grid Gap Behavior with Hidden Elements</a> by Ryan Mulligan: <q>I was recently prototyping a component layout that included a way to toggle the visibility of sibling elements inside a grid display. What tripped me up was, while these elements were hidden, all of the container's gap gutters remained, leaving undesired extra visual spacing. I expected these gutters to collapse. The reason they stick around is related to explicitly defining grid templates.</q></p>
<p>Mark Edwards on <a href="https://bjango.com/articles/opticaladjustments/">formulas for optical adjustments</a>: <q>a long-standing trope of the design world is that computers are bad at aligning and balancing the relative scales of elements. This is incorrect</q>. With the recent addition of math functions to the toolbox, a lot of these can be done in <span class="sc">CSS</span>, such as sizing images <a href="https://danburzo.ro/aspect-ratio-size/">based on their aspect ratio</a>.</p>
<h3>Engineering</h3>
<p><a href="https://vlcn.io/blog/gentle-intro-to-crdts.html">A Gentle Introduction to <span class="sc">CRDT</span>s</a>: <q>What follows is an attempt at distilling all the hard understanding work into a condensed and easy to understand set of reading for a software developer without any background in <span class="sc">CRDT</span>s or distributed systems.</q></p>
<p>Signals are a reactive state primitive, sometimes called <em>observables</em> or <em>refs</em>.
They've been recently <a href="https://preactjs.com/blog/introducing-signals/">announced for Preact</a>, but may be useful on their own: <q>At its core, a signal is an object with a <code>.value</code> property that holds some value. Accessing a signal's value property from within a component automatically updates that component when the value of that signal changes</q>. A different article discusses <a href="https://preactjs.com/blog/signal-boosting">how they were made faster</a>.</p>
<h3>From the desk of</h3>
<p>Baldur Bjarnason with <a href="https://www.baldurbjarnason.com/2023/how-i-made-my-book/">some thoughts on how to make a book</a> as a self-published author, from editing to producing the digital artifact.</p>
<p><a href="https://tomcritchlow.com/2023/02/10/riffs/">Writing, Riffs & Relationships</a>, Tom Critchlow on a genre of "small-b" blogging that invites conversation: <q>Writing on the internet is a superpower, but it can feel difficult - it can feel <em>heavy</em>. In this post I want to talk specifically about a format for writing that is deliberately designed to be easy to write, while being effective at creating connections (not pageviews).</q></p>
<p><a href="https://www.marksimonson.com/notebook/view/1979">1979</a> by Mark Simonson: <q>The digital world has its place. You can do amazing things in it (like publishing a blog post). But don’t spend all your time there. It’s not the real world.</q></p>
<h2>Tools and resources</h2>
<p>I've noticed quite a few blog posts this year referencing Julia Cameron and her concept of <em>morning pages</em>. She has a new book out called <a href="https://juliacameronlive.com/books-by-julia/write-for-life/"><em>Write for Life</em></a>, which may be good. (Despite having carried it in my backpack these past few weeks, I haven't managed to crack open my copy.)</p>
<p><a href="http://typedesignresources.com/">Type Design Resources</a> by Justin Penner is <q>a growing, public, collaborative collection of type design resources. Everything from learning the basics to running your own foundry.</q></p>
<p><q>So, you want to build an app that has its data co-located with its UI? That works offline? That synchronizes between clients? And that lets its users own their data? Welcome to the world of</q> <a href="https://localfirstweb.dev/">Local-First Web Development</a>.</p>
<p><a href="https://platformplaybook.xyz/">The Government as a Platform Playbook</a> by Richard Pope, <q>a guide to help digital government practitioners implement common digital infrastructure successfully.</q></p>
<p><a href="https://thehistoryoftheweb.com/book/">Vague, But Exciting</a>, the story of the World Wide Web as told by Jay Hoffmann.</p>
<p><a href="https://github.com/samim23/polymath">polymath</a> by Samim Winiger: <q>Convert any music library into a music production sample-library with ML</q>.</p>
<p><a href="https://bundlescanner.com/">Bundle Scanner</a> by Markus Englund <q>will fetch every Javascript file from the website and search through the files for code that matches any of the 101,962 releases it has indexed from 35,003 of the most popular npm libraries on the web.</q></p>
<p><a href="https://www.magentaa11y.com/">MagentaA11y</a>, an accessibility tool to <q>automatically generate test cases for Web, iOS and Android components</q>, originally made by Charlie Triplett.</p>
<p><strong>Kirby + Eleventy.</strong> Bastian Allgeier, creator of the Kirby <span class="sc">CMS</span>, has shared a couple of demo setups for pairing the two: <a href="https://github.com/getkirby/eleventylab">eleventylab</a> is an <q>experimental 11ty setup that reads from Kirby's content folder</q>, while <a href="https://github.com/getkirby/eleventykit">eleventykit</a> uses Kirby as a headless <span class="sc">CMS</span>. The latter is used in the <a href="https://getkirby.com/docs/cookbook/setup/headless-kiosk-application">Headless Kiosk Application</a> guide.</p>
<p><strong>Dispatches from the itch-scratching club.</strong> The front-matter data of several hundred Markdown files needed to be reorganized beyond what could be achieved with search and replace. <a href="https://github.com/mikefarah/yq"><code>yq</code></a> seemed up to the task, but it shares its syntax with <a href="https://github.com/stedolan/jq"><code>jq</code></a> — which, despite my long-standing admiration for the tool, I have not been able to grok to any useful extent. So I built <code>yamatter</code>, a command-line tool that lets you transform <span class="sc">YAML</span> front-matter data via JavaScript functions. <a href="https://github.com/danburzo/yamatter">Give it a try</a>.</p>
<h2>Today I learned</h2>
<h3>Color me surprised</h3>
<p>Sagely nodding through the very entertaining <a href="https://emnudge.dev/blog/perfect-rgb-regex/">Writing The Perfect <span class="sc">RGB</span> Regex And Failing</a> with my <em>you know, I'm something of a <span class="sc">RGB</span> regex writer myself</em> hat on, I realized midway through that <a href="https://culorijs.org/">culori.js</a> may be failing on a few of the more exotic patterns afforded by the <span class="sc">CSS</span> parsing algorithm:</p>
<pre class="language-js"><code class="language-js"><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">'color: rgb(1-2-3)'</span><span class="token punctuation">)</span> <span class="token comment">// => true</span><br />culori<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token string">'rgb(1-2-3)'</span><span class="token punctuation">)</span> <span class="token comment">// => undefined</span></code></pre>
<p>Huh, back to the drawing board.</p>
<h3>When life gives you Menlos</h3>
<p>The <code>font-family: monospace</code> declaration tells the browser to reach for its default fixed-width typeface. On macOS that's <a href="https://en.wikipedia.org/wiki/Menlo_(typeface)">Menlo</a> in Firefox and <a href="https://en.wikipedia.org/wiki/Courier_(typeface)">Courier</a> in Chrome. In Safari it's, interestingly and intentionally, either Menlo or Courier, <a href="https://bugs.webkit.org/show_bug.cgi?id=215121">depending on the <code>lang</code> attribute</a> of the element being styled.</p>
<p>Another quirk is that elements styled with <code>font-family: monospace</code> get their font size scaled by a ratio of typically <code>0.8125</code>. With the default browser style, in the markup below, the <code><p></code> will have a computed font size of <code>16px</code>, while the <code><code></code> just <code>13px</code>, despite the <code>font-size: 1em</code> declaration:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>The results are saved to <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token value css language-css"><span class="token property">font-size</span><span class="token punctuation">:</span> 1em</span><span class="token punctuation">'</span></span></span><span class="token punctuation">></span></span>localStorage<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span>.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>The story of how that ended up being the case is old and meandering — and if I ever knew it I've long purged it from memory. For the curious, <a href="https://meyerweb.com/eric/thoughts/2010/02/12/fixed-monospace-sizing/">an article from 2010</a> by Eric Meyer points to resources further back in time. Suffice to say you can opt out of the font-size quirk by putting anything before the generic <code>monospace</code> family:</p>
<pre class="language-css"><code class="language-css"><span class="token property">font-family</span><span class="token punctuation">:</span> Menlo<span class="token punctuation">,</span> monospace<span class="token punctuation">;</span></code></pre>
<p>The veil is lifted: Menlo is actually huge. To bring back the familiar style, re-apply the <code>0.8125</code> magic ratio.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">kbd, samp, pre, code</span> <span class="token punctuation">{</span><br /> <span class="token property">font-family</span><span class="token punctuation">:</span> Menlo<span class="token punctuation">,</span> monospace<span class="token punctuation">;</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.8125em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">pre code</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Making fonts play nice together</h3>
<p>The legacy style (<code>0.8125em</code>) works well for Menlo, whose metrics are on the large side, but it's less flattering for Courier or other fixed-width fonts. What works for one font may not work for others down the font stack.</p>
<p>That's where <span class="sc">CSS</span> properties to fine-tune the <em>used value</em> of <code>font-size</code> come in. That means making the text visually larger or smaller without altering its computed size, or the value of <code>1em</code>.</p>
<p>Browser support for these properties is uneven but we can use them today, as a treat.</p>
<p>You can scale the glyphs of a particular font with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/size-adjust"><code>@font-face/size-adjust</code></a>. This is widely supported, but not very robust. A more sophisticated approach is to keep the text visually uniform, regardless of the font that gets picked from the font stack. That's the purpose of the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust"><code>font-size-adjust</code></a> property, which sounds similar, but has different semantics.</p>
<p>Pair those with <span class="sc">CSS</span> properties that override other font metrics — <a href="https://developer.mozilla.org/docs/Web/CSS/@font-face/ascent-override"><code>ascent-override</code></a>, <a href="https://developer.mozilla.org/docs/Web/CSS/@font-face/descent-override"><code>descent-override</code></a>, and <a href="https://developer.mozilla.org/docs/Web/CSS/@font-face/line-gap-override"><code>line-gap-override</code></a> — and, as Katie Hempenius explains, you have a comprehensive recipe for <a href="https://developer.chrome.com/en/blog/font-fallbacks/">improved font fallbacks</a>.</p>
<hr />
<p><em>Soundtrack:</em> <a href="https://elmichelsaffair.bandcamp.com/album/glorious-game">El Michels Affair & Black Thought — Grateful</a></p>
wsf-xxxix2023-02-07T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-02-07/<h2>News</h2>
<p><strong>CSS Color.</strong> Chrome 111 (currently Canary) is preparing support for a whole bunch of CSS Color Module 4 features, as summarized by Adam Argyle in <a href="https://developer.chrome.com/articles/high-definition-css-color-guide/">High Definition CSS Color Guide</a>. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/dynamic-range"><code>dynamic-range</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/color-gamut"><code>color-gamut</code></a> media queries mentioned in the article were new to me.</p>
<p>The release also includes things from <code>css-color-5</code>, namely <a href="https://developer.chrome.com/en/blog/css-color-mix/">the <code>color-mix()</code> function</a>.</p>
<p>Meanwhile, <a href="https://webkit.org/blog/13703/release-notes-for-safari-technology-preview-162/">Safari TP 162</a> marks the debut of the <a href="https://w3c.github.io/csswg-drafts/css-color-5/#relative-colors">relative color syntax</a>, another <code>css-color-5</code> feature. It offers a neat way to break apart any color into its components and optionally perform <code>calc()</code>s on them, or replace them with absolute values, to produce a derived color:</p>
<pre class="language-css"><code class="language-css"><span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">lch</span><span class="token punctuation">(</span>from magenta 75 <span class="token function">calc</span><span class="token punctuation">(</span>c - 50<span class="token punctuation">)</span> h<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>I started working on <a href="https://culorijs.org/">Culori</a> in 2018 with the explicit goal of offering comprehensive support for <code>css-color-4</code> and beyond. It's very exciting to see the recent momentum in browser adoption, which has the fortunate side-effect of letting me test the library against <a href="https://wpt.fyi/results/css/css-color">web platform tests</a>.</p>
<p>Moving to unspecced territory, I urge you to check out <a href="https://scrtwpns.com/mixbox/">Mixbox</a> by Šárka Sochorová and Ondřej Jamriška. It aims to replicate how physical pigments blend, to frankly gorgeous effect. The library is licensed under <span class="sc">CC BY-NC 4.0</span>, and there's also <a href="https://scrtwpns.com/mixbox.pdf">a paper</a> to dig into the math (via <a href="https://cykele.ro/">Nathan Manceaux-Panot</a>).</p>
<p><strong>The <code>ElementInternals</code> interface</strong> letting custom HTML elements participate in forms like regular inputs is also included in Safari TP 162, which means it will soon be available across major browser engines. WebKit's Ryosuke Niwa blogged some details <a href="https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/">on how that works</a>.</p>
<h2>Articles</h2>
<h3>Accessibility</h3>
<p>In <a href="https://adrianroselli.com/2022/11/your-accessibility-claims-are-wrong-unless.html">Your Accessibility Claims Are Wrong, Unless…</a>, Adrian Roselli offers <q>some approaches you can and should take before you set expectations about the accessibility of your <em>thing</em> — code or article or talk or whatever</q> including comprehensive instructions on walking the walk before talking the talk.</p>
<p>Eric Eggert enumerates the various pieces that underlie accessible web experiences and the complexity to which their interplay gives rise. It concludes with an ask to <a href="https://yatil.net/blog/fix-web-accessibility-systematically">fix web accessibility systematically</a>: <q>To make it easier to use, we have to bring accessibility back and modernize existing basic standards. It will simplify documentation, improve reliability, make errors easier to detect. And it will help to make accessibility easier to teach.</q></p>
<p>By the same author, <a href="https://yatil.net/blog/new-wcag-22-features-rated">a ranking of the new WCAG 2.2 features</a>.</p>
<h3>Web platform</h3>
<p>In general, I strive to be precise with the language I use to describe web platform features. Karl Dubost's <a href="https://www.otsukare.info/2022/10/25/css-values-definitions">"Thousand" Values of CSS</a> guide to the various kinds of CSS values — <em>specified</em>, <em>computed</em>, <em>used</em>, and more — is a helpful reference (via <a href="https://chriscoyier.net/2023/02/06/the-different-names-for-values-in-css/">Chris Coyier</a>).</p>
<p>Paul Kinlan on what it would take to make a website <a href="https://paul.kinlan.me/the-local-only-web/">truly local-only</a>: <q>You would need to provide some guarantees that data couldn't be exfiltrated out of the browser</q>. I like the idea of using CSP headers for extra assurance.</p>
<h3>Performance</h3>
<p>In <a href="https://infrequently.org/2023/02/the-market-for-lemons/">The Market for Lemons</a>, Alex Russell reflects on the decade lost to collectively chasing increasingly overwrought JavaScript-driven solutions for websites better served by simpler technology, as it happens to be the case for most websites. Beyond what is appropriate for a certain <em>type</em> of website, Alex also argues for the need to match complexity to the team's ability to keep it in check.</p>
<p>Further testament to the vibe shift, Deno <del>announces</del> promotes Fresh, its web framework, with an article titled <a href="https://deno.com/blog/the-future-and-past-is-server-side-rendering">The Future (and the Past) of the Web is Server Side Rendering</a>.</p>
<p>Marvin Hagemeister has been publishing performance insights on JavaScript-based tools. For his latest installment <a href="https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-3/">he tackles <code>eslint</code></a>, finding several <em>demonstrable</em> points of intervention. Lots to learn on performance profiling from the series (via Nolan Lawson).</p>
<h3>Writing</h3>
<p>I've struggled with each of the hurdles mentioned by Max Böck in <a href="https://mxb.dev/blog/seven-reasons-why-i-dont-write/">7 reasons why I don't write</a>, but I'm happy I've at least crossed out the tech part. The website is now dutifully humming along and I can focus my entire energy on being disappointed with my writing style and/or frustrated with the time it takes me to produce ideas in the right shape. It's not <em>all</em> bad, though!</p>
<p><a href="https://rachelandrew.co.uk/archives/2023/01/28/technical-writing-resources/">Technical writing resources</a> compiled by Rachel Andrew, useful <q>whether you are interested in technical writing as a career, or just want to be able document your code, or communicate better</q>.</p>
<h2>Tools and resources</h2>
<p>Peter van Hardenberg announces <a href="https://automerge.org/blog/automerge-2/">the release of Automerge 2.0</a>, <q>a production-ready <span class="sc">CRDT</span> with huge improvements in performance and reliability</q>.</p>
<p><a href="https://web.dev/learn/images/">Learn images</a>, an in-depth course on images for the web from Mat Marquis. <q>You can go through the series from start to finish for a holistic understanding of how images work on the modern web, or use it as a reference for the specific concepts and markup patterns you’ll be using in your day-to-day work.</q></p>
<p><a href="https://tomcritchlow.com/2023/01/27/small-databases/">The Magic of Small Databases</a> by Tom Critchlow: <q> Publishing documents to the web is a well-served use case but publishing small indexes, databases and collections to the web is still an incredibly frustrating and under-served use case. Here I outline why I think it matters and a variety of approaches to solving it.</q></p>
<p>Tom's article includes <a href="https://airtable.com/shrYY94GrqVB4HUsi/tblHPrdomiPbLpod6/viwxizssDJMsGqhg9?backgroundColor=green&blocks=hide">a spreadsheet of useful tools</a>, in which I found <a href="https://collectionbuilder.github.io/">CollectionBuilder</a>, an <q>open source framework for creating digital collection and exhibit websites that are driven by metadata and powered by modern static web technology</q> (i.e. Jekyll). The project is part of <a href="https://lib-static.github.io/">Lib-Static</a>, <q>a provocation to rethink how we do digital infrastructure in libraries to recenter our technology choices around sustainable, pragmatic, and minimal approaches</q>.</p>
<p>Relatedly, <a href="https://pagefind.app/">Pagefind</a> is <q>a fully static search library that aims to perform well on large sites, while using as little of your users’ bandwidth as possible, and without hosting any infrastructure</q>. On multilingual websites, it reads the <code>lang</code> attribute of <code><html></code> elements and each language gets its own index. Neat! Robb Knight wrote an article about <a href="https://rknight.me/using-pagefind-with-eleventy-for-search/">using PageFind with Eleventy for search</a>.</p>
<p><strong>Language tools for font engineers.</strong> <a href="https://hyperglot.rosettatype.com/">Hyperglot</a> by Rosetta Type helps you <q>find which languages and how many speakers your font supports and review any additional design requirements</q>. The online tool is supplemented by a command-line interface and Python package, <a href="https://github.com/rosettatype/hyperglot">available on GitHub</a> (via Colin M. Ford).</p>
<p><a href="https://www.setuptype.com/x/cod/">Context of Diacritics</a> by Ondrej Jób is <q>an analysis of diacritics made to help type designers with refining the character sets of their fonts</q> that organizes individual accented characters, and character combinations, by frequency of use.</p>
<p><strong>Upcoming books.</strong> Marcin Wichary's <a href="https://shifthappens.site/"><em>Shift Happens</em></a> <q>tells the story of keyboards like no book ever before, covering 150 years from the early typewriters to the pixellated keyboards in our pockets</q>. In an impressive collective act of instabacking, the two-volume book exceeded <a href="https://www.kickstarter.com/projects/mwichary/shift-happens">its Kickstarter goal</a> in the span of a few hours.</p>
<p>Pair it with Keith Houston's <a href="https://wwnorton.com/books/9780393882148"><em>Empire of the Sum</em></a>, which traces the rise of the pocket calculator and is due later this year. In the meantime, the <a href="https://shadycharacters.co.uk/2021/02/announcing-a-new-book-empire-of-the-sum/">author's announcement</a> from a while back.</p>
<h2>Today I Learned</h2>
<p><strong>Shortcuts on the Mac.</strong> Initially released for iOS, the Shortcuts app was made available for macOS as a modern replacement for the old Automator. I've recently packaged with it, as a Quick Action for Finder, a <a href="https://danburzo.ro/toolbox/aliases/">shell command</a> I use all the time to grab <code><img></code> tags with the correct width and height. Here's what it looks like:</p>
<figure>
<img loading="lazy" src="https://danburzo.ro/img/wsf/magick-shortcut.png" width="470" height="500" alt="The main area the macOS Shortcuts interface, divided in boxes corresponding to the steps taken by the shortcut. The first step is to accept as input one or more images via the Quick Actions menu. The second step is to run a shell command, configured to accept the images as arguments to the command. The third step is to split the shell output into individual lines. The fourth and final step is to copy the lines to the clipboard." />
<figcaption>
<p>The editing interface of a macOS shortcut. Shown here is the <em>Copy as HTML</em> Quick Action that uses <a href="https://imagemagick.org/">ImageMagick</a> (<code>magick</code>) to produce HTML <code><img></code> tags for the selected images.</p>
</figcaption>
</figure>
<p>In contrast to Automator, the Shortcuts app has all sorts of friction points designed to dissuade people from running unsound code on their machines. Shell scripts need to be enabled via a preference, and the shortcuts ask for permission to read files and write to the clipboard.</p>
<p>You can put these shortcuts in a variety of places, including the Menu Bar, Share Sheets, the Quick Action menu, or the Dock. Pretty powerful stuff for little pieces of ambient automation here and there. If I was really serious about exploring Shortcuts, I would probably grab Rosemary Orchard's <a href="https://www.takecontrolbooks.com/shortcuts/">Take Control of Shortcuts</a> book, now in a recently-updated second edition.</p>
<hr />
<p><em>Soundtrack:</em> I am not allowed to play Evgueni Galperine's latest album, <em>Theory of Becoming</em>, in the presence of others because apparently it creates a vague sense of dread giving everyone anxiety. <a href="https://www.youtube.com/watch?v=5LuvkchnIBk">Headphones recommended</a>.</p>
<p><em>Iberic goods:</em> A visit to Barcelona earlier this year left us inconsolably hooked on <em>mango deshidratado</em> from Mercadona and tea from Sans & Sans.</p>
wsf-xxxviii2023-01-27T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-01-27/<h2>News</h2>
<p><strong>Chrome 109</strong> ships with support for <a href="https://developer.mozilla.org/en-US/docs/Web/MathML">MathML</a>! Yes, that MathML, the XML-based language to describe math. <q>It took about 25 years to get it done</q>, says Brian Kardell, <q>but hey, we did it</q>. See <a href="https://www.igalia.com/2023/01/10/Igalia-Brings-MathML-Back-to-Chromium.html">Igalia's announcement</a> for more details.</p>
<p>Also included in the release is some good news for variable fonts: the <code>font-weight</code>, <code>font-style</code> and <code>font-stretch</code> descriptors get a new <code>auto</code> default value, which reads the appropriate axis range from the font's data. That means you can safely omit these from your <code>@font-face</code> declaration without causing the browser to do the weird font synthesis of which <a href="https://danburzo.ro/variable-fonts/">I learned the hard way</a>.</p>
<p>A small but neat addition: the <code>lh</code> CSS unit (short for <em>line-height</em>), which has some <a href="https://danburzo.ro/line-height-lh/">interesting properties</a>. Still in niche typographic features, Chrome 110 (now in beta) will support <a href="https://developer.chrome.com/en/blog/control-your-drop-caps-with-css-initial-letter/">the <code>initial-letter</code> property</a> that replaces the decades-old <code>float:left</code> approach for drop-cap effects. I reckon their release in quick succession means <code>initial-letter</code> uses <code>lh</code> math under Chromium's hood.</p>
<p><strong>Firefox 109</strong> introduces support for <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event">the <code>scrollend</code> event</a>, a handy counterpart to the <code>scroll</code> event that replaces unreliable timer-based hacks. The event is also coming <a href="https://developer.chrome.com/en/blog/scrollend-a-new-javascript-event/">in Chrome 111</a>.</p>
<p>Firefox also joins Chrome in supporting <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility">the <code>content-visibility</code> property</a> that improves performance by not painting elements that are <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Containment#relevant_to_the_user">not relevant to the user</a>.</p>
<p><strong>WebKit's</strong> <a href="https://wpewebkit.org/blog/05-new-svg-engine.html">new SVG engine</a> is shaping up nicely. It promises to make rendering snappier, while unifying many aspects with HTML/CSS and unlocking new possibilities, such as <code>z-index</code> for SVG elements.</p>
<p><strong>MDN</strong> has added a section that tracks <a href="https://developer.mozilla.org/en-US/plus/updates">updates in browser support</a> for web platform features, a kind of <a href="https://caniuse.com/">Can I Use</a> as an RSS feed.</p>
<p><strong>The HTML spec</strong> for the <code><dialog></code> element was recently expanded to address concerns around initial focus. These updates may tip the scale away from custom solutions, as Scott O'Hara observes in <a href="https://www.scottohara.me/blog/2023/01/26/use-the-dialog-element.html">Use the dialog element (reasonably)</a>.</p>
<p><strong>Eleventy 2.0</strong> is <a href="https://www.11ty.dev/blog/eleventy-v2-beta/">now in beta</a>. Stephanie Eckles helpfully goes over <a href="https://11ty.rocks/posts/new-features-upgrade-considerations-eleventy-version-2/">new features and upgrade considerations</a>, and Lene Saile explains how to use the new i18n plugin for <a href="https://www.lenesaile.com/en/blog/internationalization-with-eleventy-20-and-netlify/">internationalization with Eleventy 2.0 and Netlify</a>. Upgrading this website to the beta seems to have been seamless, but do let me know if you spot anything funky.</p>
<h2>Articles</h2>
<h3>HTML, CSS</h3>
<p><a href="https://ishadeed.com/article/conditional-css/">Conditional CSS</a>, in which Ahmad Shadeed delineates a set of CSS features that react to variations in state or browsing circumstances.</p>
<p><a href="https://hidde.blog/flex-grow-illustration/">Data-informed flex-grow for illustration purposes</a>, Hidde de Vries on using CSS flex items to draw books on a bookshelf depending on their page count.</p>
<p><a href="https://www.smashingmagazine.com/2023/01/level-up-css-skills-has-selector/">Level Up Your CSS Skills With The <code>:has()</code> Selector</a>, some advanced techniques by Stephanie Eckles.</p>
<p>Robin Rendle observes how <a href="https://www.robinrendle.com/notes/container-queries-and-typography/">container queries make typography</a> <q>no longer a series of cool hacks and instead now a fully fledged citizen in the browser as of 2023</q>.</p>
<p><a href="https://adactio.com/journal/19842">Three attributes for better web forms</a> by Jeremy Keith: <q>Forms on the web are an opportunity to make big improvements to the user experience with very little effort. The effort can be as little as sprinkling in a smattering of humble HTML attributes. But the result can be a turbo-charged experience for the user, allowing them to sail through their task.</q></p>
<p><a href="https://codepen.io/scottjehl/pen/abJrPOP">A responsive table</a> with fixed column and row headers and scroll snap, all done with plain old HTML table markup and a sprinkle of newer CSS by Scott Jehl.</p>
<p>Steve Faulkner summarizes real-life <a href="https://www.tpgi.com/screen-readers-support-for-text-level-html-semantics/">Screen Readers support for text level HTML semantics</a>, or how assistive technology responds to the usage of <code><b></code> vs. <code><strong></code>, <code><i></code> vs. <code><em></code>, <code><u></code>, <code><ins></code>, <code><del></code>, and <code><mark></code>.</p>
<p>The GOV.UK Design System team have documented their <a href="https://design-system.service.gov.uk/community/accessibility-strategy/">accessibility strategy</a>, about which you can learn more in the <a href="https://accessibility.blog.gov.uk/2023/01/06/a-new-accessibility-strategy-for-the-gov-uk-design-system/">introductory post</a>.</p>
<p><a href="https://medium.com/the-readability-group/a-guide-to-understanding-what-makes-a-typeface-accessible-and-how-to-make-informed-decisions-9e5c0b9040a0">A Guide to Understanding What Makes a Typeface Accessible</a> by Gareth Ford Williams. See also: <a href="https://www.youtube.com/watch?v=h8IOqUl1zII">Don’t Believe The Type!</a>.</p>
<h3>JavaScript, DOM</h3>
<p>Jake Archibald illustrates <a href="https://jakearchibald.com/2023/unhandled-rejections/">the gotcha of unhandled promise rejections</a>, which is kind of baking my noodle at this moment in time, but sounds fun to think through.</p>
<p><a href="https://patrickbrosset.com/articles/2023-01-17-web-storage/">(Almost) everything about storing data on the web</a>, a summary by Patrick Brosset.</p>
<h3>Browsers</h3>
<p>Watch Nolan Lawson's deeply researched <a href="https://nolanlawson.com/2023/01/17/my-talk-on-css-runtime-performance/">talk on CSS runtime performance</a>: <q>my main goal was to shine a light on all the heroic work that browser vendors have done over the years to make CSS so performant</q>. Coincidentally, Edge 109 has shipped a <em>Selector Stats</em> section in its DevTools, which Patrick Brosset uses to <a href="https://blogs.windows.com/msedgedev/2023/01/17/the-truth-about-css-selector-performance/">optimize some expensive CSS selectors</a>.</p>
<p><a href="https://www.geoffreylitt.com/2023/01/08/for-your-next-side-project-make-a-browser-extension.html">For your next side project, make a browser extension</a>, Geoffrey Litt on expanding Twitter's functionality with a browser add-on, and on the effort you save by going that route.</p>
<p>In <a href="https://obyford.com/posts/the-safari-bug-that-never-was/">The Safari bug that never was</a>, Oliver Byford tells the story of reporting that <q>Text wraps unnecessarily within intrinsically-sized elements when using certain fonts and the inner HTML of the element contains a new line that is not preceded by a space</q> [<a href="https://bugs.webkit.org/show_bug.cgi?id=232939">WebKit#232939</a>].</p>
<h3>Making things</h3>
<p><a href="https://henry.codes/writing/how-to-make-a-website/">How To Make a Website</a> by Henry Desroches: <q>Write meaningful HTML that communicates the structure of your document before any style or additional interactivity has loaded. Write CSS carefully, reason your methodology and stick to it, and feel empowered to skip frameworks. When it comes time to write JavaScript, write not too much, make sure you know what it all does, and above all, make sure the website works without it.</q></p>
<p><a href="https://andy-bell.co.uk/front-end-is-so-much-more-than-building-designs/">Front-end is so much more than building designs</a>, in which Andy Bell responds to the hypothetical scenario: <q>a high-fidelity design is plopped on my desk and I’m asked to build it</q>.</p>
<p><a href="https://www.nytimes.com/games/wordle/index.html">Wordle</a> enthused lots of people about word games — and even about maybe inventing one. <a href="https://www.theguardian.com/lifeandstyle/2022/dec/22/i-was-asked-to-invent-the-next-wordle-how-hard-could-it-be">How hard could it be?</a>, asks The Guardian's David Shariatmadari (via <a href="https://uxdesign.cc/trying-to-design-the-next-wordle-122f2049c426">Clive Thompson</a>). The result is <a href="https://www.wordiply.com/">Wordiply</a>. Jer Thorp has also made one called <a href="https://tumbleword.glitch.me/">Tumbleword</a>.</p>
<p>For a comprehensive list of English words to power your creation, look no further than <a href="https://github.com/lorenbrichter/Words">lorenbrichter/Words</a>, originally compiled for Atebits' Letterpress game.</p>
<p>Speaking of brilliant word games, <a href="https://en.wikipedia.org/wiki/Codenames_(board_game)">Codenames</a> has been a hit with basically all friends and family whom I've witnessed play it. Apparently, there's also a free <a href="https://codenames.game/">web version</a> with cards in all languages in which the game has been published — a very wholesome move from the creators.</p>
<p>David Aerne, prolific author of color-adjacent tools, gives us a tour of his work in <a href="https://elastiq.notion.site/elastiq/Color-My-Journey-Through-the-Spectrum-2cfd8dc540474b1c9ef381b9a3bc0f8e">Color — My Journey Through the Spectrum</a>.</p>
<p>Nolan Lawson on <a href="https://nolanlawson.com/2023/01/09/retiring-pinafore/">retiring Pinafore</a>, a stellar Mastodon web client, after five years: <q>I don’t have the energy to do this anymore. Pinafore has gone from being a fun side project to being a source of dread for me. There is a constant stream of bug reports, feature requests, and pull requests to manage, and I just don’t want to spend my free time doing this anymore.</q> What follows are interesting notes on the impact of tech choices, pockets of unexpected complexity, and lessons learned.</p>
<h2>Tools and Resources</h2>
<p>Nicholas Rougeux has released a new project: <a href="https://www.c82.net/architecture/">a digital version</a> of Andrea Palladio's seminal sixteenth-century treatise <em>The Four Books of Architecture</em>. As we've come to expect, the project is accompanied by an <a href="https://www.c82.net/blog/?id=91">insightful write-up</a>.</p>
<p><a href="https://github.com/ivanreese/visual-programming-codex">Visual Programming Codex</a> by Ivan Reese: <q>Despite the GUI going mainstream in the 80s, the programming community has largely opted to remain in the era of the terminal. Yet there have always been magnificent visual programming projects at the periphery. This repo exists to shine a light on these alternatives, see what costs and benefits they offer, and reflect on the work of people pushing for a future where programming leverages more of our senses and modalities.</q></p>
<p><a href="https://pythonfordesigners.com/">Python for designers</a>, a stylish introduction to creative coding with Python 3 and <a href="https://www.drawbot.com/">DrawBot</a> by Roberto Arista.</p>
<p><a href="https://www.webpagetest.org/learn/lightning-fast-web-performance/">Lightning-Fast Web Performance</a>, an online course from Scott Jehl that teaches you to <q>analyze site performance, fix issues, monitor for regressions, and deliver fast, responsive designs from the start.</q></p>
<p><a href="https://bookcoverreview.co.uk/">The Book Cover Review</a> by David Pearson, <q>500-word* reviews of beloved book covers – both new and old – from a range of voices around the world.</q></p>
<p><strong>Details in typeface licensing.</strong> Miles Newlyn <a href="https://typedrawers.com/discussion/4660/widespread-misunderstanding-of-adobetypes-license-causing-foundries-to-loose-revenue">points out</a> two widespread misconceptions about the Adobe Fonts license that lose foundries money: (A) that it's okay to host the webfonts yourself, and (B) that you can use your subscription to serve webfonts for clients' websites. Relatedly, Jeff Frankl <a href="https://subsetting.xyz/">summarizes foundries' stances</a> toward the subsetting of their webfonts.</p>
<p>Over in OFL-licensed typefaces, I was curious if anyone had fixed the quirky <code>q</code> glyph in <a href="https://fonts.google.com/specimen/Karla">Karla</a>, and was happy to find <a href="https://github.com/ms-studio/karmilla">Karmilla</a> addresses it, along with other refinements.</p>
<p><a href="https://github.com/typst/svg2pdf">typst/svg2pdf</a> is a Rust crate and CLI tool that converts SVGs to PDF without rasterizing them.</p>
<p><a href="https://howfuguismybrowser.dev/">How Fugu is my browser?</a> by Thomas Steiner checks how many native-like web APIs your current browser supports.</p>
<h2>Today I Learned</h2>
<p><strong>The <code>:has()</code> CSS pseudo-class</strong> has been recently changed to be unforgiving, <a href="https://css-tricks.com/has-is-an-unforgiving-selector/">as reported</a> by Geoff Graham, which is to say its argument works like a normal selector list, becoming invalid altogether when any of the selectors in the list is invalid. On the other hand, the <code>:is()</code> and <code>:where()</code> pseudo-classes remain forgiving.</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* invalid */</span><br /><span class="token selector">button, ::buttonlike</span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span><br /><span class="token selector">form:has(button, ::buttonlike)</span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span><br /><br /><span class="token comment">/* valid */</span><br /><span class="token selector">:where(button, ::buttonlike)</span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span><br /><span class="token selector">:is(button, ::buttonlike)</span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span></code></pre>
<hr />
<p><em>Soundtrack:</em> <a href="https://www.youtube.com/watch?v=HpxEXMIY64c">Fever Ray — Kandy</a>, old-school The Knife vibes.</p>
wsf-xxxvii2023-01-06T00:00:00Zhttps://danburzo.ro/watchstarfork/2023-01-06/<h2>News</h2>
<p>WordPress is <a href="https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/">getting support for SQLite</a>, which promises to make small websites faster and less resource-intensive, as well as more portable and easier to back up. Relatedly, <a href="https://developer.wordpress.org/playground/">WordPress Playground</a> lets you <q>experience a WordPress that runs entirely in your browser</q>, a not entirely clear but nonetheless intriguing proposition (via <a href="https://css-tricks.com/wordpress-playground-run-in-browser/">Geoff Graham</a>).</p>
<h2>Articles</h2>
<p><a href="https://100r.co/site/weathering_software_winter.html">Weathering Software Winter</a> by Hundred Rabbits, the transcript of <a href="https://www.youtube.com/watch?v=9TJuOwy4aGA">a talk</a> given by Devine Lu Linvega at Handmade Seattle 2022 on software simplicity, personal computing, and digital preservation.</p>
<p><a href="https://infrequently.org/2022/12/performance-baseline-2023/">The Performance Inequality Gap, 2023</a> by Alex Russell. <q>How did this happen? Well, per the new usual, overly optimistic assumptions about the state of the world accreted until folks at the margins were excluded.</q></p>
<p>A reminder from Rachel Andrew to <a href="https://rachelandrew.co.uk/archives/2022/11/04/stop-treating-all-of-your-content-as-if-it-were-news/">stop treating all of your content as if it were news</a>. The tension between time-bound vs. evergreen content is something with which I've also struggled when organizing this website not least because, even if <a href="https://chriscoyier.net/2023/01/04/feeds-can-be-whatever/">RSS feeds can be whatever</a>, they are still time-oriented.</p>
<p>Stephanie Eckles' <a href="https://12daysofweb.dev/">12 days of web</a> gathers some great deep-dives into HTML, CSS, and DOM topics from an array of contributors.</p>
<p>When it comes a sticky element, at least two things can happen: either it obscures important content underneath, or you find random elements (which, by virtue of some style declaration, have become <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">stacking contexts</a>) casually floating along on top of it as you scroll. James Edwards offers a JavaScript-based solution for the former case in: <a href="https://www.tpgi.com/prevent-focused-elements-from-being-obscured-by-sticky-headers/">Prevent focused elements from being obscured by sticky headers</a>. (See also <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1591940">Firefox#1591940</a>)</p>
<p>Responsive CSS that responds to the wrong thing can cause elements to behave the opposite of what's expected when the user zooms in and out of the page. One such example is fluid typography tied to the viewport width, whose issues Adrian Roselli <a href="https://adrianroselli.com/2019/12/responsive-type-and-zoom.html">has helpfully documented</a>. Sensible advice: <q>If you are going to use responsive typography techniques anyway, you must test it by zooming. Zoom across devices, across browsers, across viewport sizes (not everyone surfs full-screen), and across viewport orientations</q> (via <a href="https://www.smashingmagazine.com/2022/12/fluid-typography-predict-problem-users-zoom-in/">Ruslan Yevych</a>).</p>
<p><a href="https://pepelsbey.dev/articles/skewed-highlight/">A CSS challenge: skewed highlight</a> by Vadim Makeev, using the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/box-decoration-break"><code>box-decoration-break</code></a> CSS property.</p>
<p><a href="https://sheep.horse/2022/12/pixel_accurate_atkinson_dithering_for_images_in_ht.html">Pixel Accurate Atkinson Dithering for Images in HTML</a>, Andrew Stephens on making <code><as-dithered-image></code>, a custom element for responsive images dithered on the fly. Probably not something I would deploy in production, but deeply satisfying to look at.</p>
<p><a href="https://muffinman.io/blog/draw-svg-rope-using-javascript/">Draw SVG rope using JavaScript</a>, an interactive article by Stanko Tadić.</p>
<p>Riley Cran on Lettermatic's <a href="https://lettermatic.com/custom/pentiment">meticulous type design for the game Pentiment</a>, which hopefully gets released for more platforms because it looks like <a href="https://pentiment.obsidian.net/">totally my kind of thing</a>.</p>
<p><a href="https://borretti.me/article/unbundling-tools-for-thought">Unbundling tools for thought</a> by Fernando Borretti: <q>everything I can do with a personal wiki I can do better with a specialized app, and the few remaining use cases are useless. Let’s break it down.</q></p>
<p>Max Kohler on implementing <a href="https://www.maxkohler.com/posts/calendar-links/">Add-to-calendar links</a>, <q>a simple, HTML-only way to enhance event websites</q>. Relatedly, you make passes for your iOS Wallet with the <a href="https://en.wikipedia.org/wiki/PKPASS"><code>.pkpass</code> format</a>.</p>
<p>Making some sort of ambient thing with e-ink is one of my most unwavering <em>someday</em> projects. Until I get to it, here's another entry in the reference folder: <a href="https://kimmo.blog/posts/7-building-eink-weather-display-for-our-home/">Building an e-ink weather display for our home</a> by Kimmo Brunfeldt.</p>
<h2>Tools and Resources</h2>
<p><a href="https://hypermedia.systems/">Building Hypermedia Systems</a>, <q>a simpler approach to building applications on the Web and beyond with htmx and HyperView</q> by Carson Gross, Adam Stepinski, and Deniz Akşimşek.</p>
<p><a href="https://htmlwithsuperpowers.netlify.app/">HTML with Superpowers</a>, a new introduction to Web Components by Dave Rupert that supplements his <a href="https://frontendmasters.com/courses/web-components/">4-hour video course</a>.</p>
<p><a href="https://web.dev/new-patterns-for-amazing-apps/">New patterns for amazing apps</a>, code snippets by Thomas Steiner for working with recent native-like Web APIs.</p>
<p>Marc Edwards now has a <a href="https://bjango.com/articles/iconspeedrunvideos/">YouTube channel</a> with video versions of his Adobe Illustrator <a href="https://bjango.com/articles/">icon speedrun articles</a>.</p>
<p>Jean-Paul Delahaye's <em>Dessins géométriques et artistiques avec votre micro-ordinateur</em> (1985), <a href="https://github.com/v3ga/dessins_geometriques_et_artistiques">recoded in p5.js</a> by Julien Gachadoat.</p>
<p><a href="https://hyperpaper.me/">Hyperpaper Planner</a> is <q>a fully interlinked dayplanner designed for tablet devices, contained in a single PDF file</q>. Such an ingenious idea.</p>
<h2>Today I Learned</h2>
<p><strong>A robust JavaScript method for splitting text</strong> into characters (more specifically <em>grapheme clusters</em> or <em>user-perceived characters</em>), words and sentences with the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter"><code>Intl.Segmenter</code></a> interface (via <a href="https://www.stefanjudis.com/today-i-learned/how-to-split-javascript-strings-with-intl-segmenter/">Stefan Judis</a>). Basic usage:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> segmenter <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Intl<span class="token punctuation">.</span>Segmenter</span><span class="token punctuation">(</span><span class="token string">'en'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">granularity</span><span class="token operator">:</span> <span class="token string">'word'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> segments <span class="token operator">=</span> segmenter<span class="token punctuation">.</span><span class="token function">segment</span><span class="token punctuation">(</span><span class="token string">'Hello World!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">[</span><span class="token operator">...</span>segments<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> item<span class="token punctuation">.</span>segment<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// => ["Hello", " ", "World", "!"]</span></code></pre>
<p>Pretty cool for text analysis. And when <code>granularity: "word"</code>, each segment comes with an <code>isWordLike</code> boolean to distinguish between actual words and spaces or punctuation.</p>
<blockquote>
<p><em>For the curious:</em> General rules for breaking up text into significant units are defined in Unicode Standard <a href="https://www.unicode.org/reports/tr29/">Annex 29</a>, and for breaking text into lines in <a href="https://www.unicode.org/reports/tr14/">Annex 14</a>. Language-specific tailorings are provided by the Unicode <a href="https://cldr.unicode.org/">Common Locale Data Repository</a> Project.</p>
</blockquote>
<p><strong>Getting answers out of browser console snippets.</strong> For quick-and-dirty text analysis, <code>Intl.Segmenter</code> simplified <a href="https://llll.ro/meta/analiza/">a couple of snippets</a> I had written for running in the browser console to extract unique words out of books and to count their frequency.</p>
<p>As a way to communicate the result back to the user, <code>console.log()</code> can sometimes be unwieldy. Instead I use the <code>copy()</code> function to put the answer into the clipboard. The obvious downside is that, unless you run it off a bookmarklet, the snippet gets overwritten each time it gets run. I wondered if there's a nicer (but still succinct) way. How about putting the result on a separate webpage?</p>
<pre class="language-js"><code class="language-js"><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><br /> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><br /> <span class="token punctuation">[</span><span class="token string">'Știri din țară'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'text/plain'</span><span class="token punctuation">}</span><br /> <span class="token punctuation">)</span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// => blob:https://danburzo.ro/d9dafec6-d590-4c89-8473-b12413888949</span></code></pre>
<p>Close! But this <code>blob:</code> URL is only clickable in Chrome, so in Firefox and Safari you're stuck with either copying the URL to the clipboard (thus negating most of the benefit over <code>copy()</code>) or, with some dexterity, selecting the text and drag-and-dropping it onto the New Tab icon.</p>
<p>More important than ergonomics is what happens when you open the URL of a blob adorned with diacritics:</p>
<pre><code>Știri din țară
</code></pre>
<p><span id="text-plain-default-encoding">Oops!</span> <a href="https://www.rfc-editor.org/rfc/rfc6657.html">The default character encoding</a> for the <code>text/plain</code> MIME type is actually <code>US-ASCII</code>, so the correct snippet is the one below. Note that Safari 16.2 currently <a href="https://bugs.webkit.org/show_bug.cgi?id=243953">ignores the charset</a>, but it's thankfully fixed in Safari Technology Preview, so it won't matter soon. (<em>Update:</em> it has been fixed in Safari 16.3.)</p>
<pre class="language-js"><code class="language-js"><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><br /> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><br /> <span class="token punctuation">[</span><span class="token string">'Știri din țară'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'text/plain;charset=utf8'</span><span class="token punctuation">}</span><br /> <span class="token punctuation">)</span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<hr />
<p><em>WSF year in review</em>: This year I managed just four issues, fewer than 2021 (ten) but much better than 2020 (zero).</p>
<p><em>Soundtrack</em>: I must have played this <a href="https://www.youtube.com/watch?v=sWZi4ml1a4k">wonderful vocal version</a> of Roger Eno's <em>Bells</em>, featuring Cecily and Lotti Eno, for at least a few dozen times.</p>
wsf-xxxvi2022-12-17T00:00:00Zhttps://danburzo.ro/watchstarfork/2022-12-17/<h2>News</h2>
<p>The Computer History Museum in Mountain View, California is making available the source code for Adobe PostScript. More context in <a href="https://computerhistory.org/blog/postscript-a-digital-printing-press/">PostScript: A Digital Printing Press</a>.</p>
<p>CSS parsing rules don't leave enough elbow room for <a href="https://drafts.csswg.org/css-nesting/">CSS Nesting</a> to have a syntax as straightforward as everyone would like. Jen Simmons invites authors to <a href="https://webkit.org/blog/13607/help-choose-from-options-for-css-nesting-syntax/">weigh in on their preferred alternative</a>.</p>
<p><a href="https://wordpress.org/news/2022/11/misha/">WordPress 6.1 “Misha”</a> introduces <a href="https://wordpress.org/news/2022/11/introducing-twenty-twenty-three/">Twenty Twenty-Three</a>, a new <em>block</em> theme — which is to say it supports <a href="https://www.smashingmagazine.com/2022/10/wordpress-full-site-editing/">Full-Site Editing</a>, introduced in WordPress 5.9. Along with <a href="https://css-tricks.com/getting-started-with-wordpress-block-development/">developing your own blocks</a>, these features should enable new ways of building WordPress websites.</p>
<p>SvelteKit, the framework for building websites with Svelte, has just seen its <a href="https://svelte.dev/blog/announcing-sveltekit-1.0">inaugural stable release</a>.</p>
<h2>Articles</h2>
<p><a href="https://www.kryogenix.org/days/2022/08/31/farmbound-or-how-i-built-an-app-in-2022/">Farmbound, or how I built an app in 2022</a>, Stuart Langridge on building and publishing a small web game. A gentle reminder that not every project needs a custom domain name: <q>I don’t really want to be on the hook forever for paying for a domain; sure, it’s not much money, but it’s still annoying that I’m paying for a couple of ideas that I had a decade ago and which nobody cares about any more. I can’t drop them, because of course cool URIs don’t change, and I didn’t want to be thinking a decade from now, do I still need to pay for this?</q></p>
<p><a href="https://utopia.fyi/blog/designing-a-utopian-layout-grid">Designing a Utopian layout grid: Working with fluid responsive values in a static design tool</a> by James Gilyead <q>aims to contextualise the <a href="https://utopia.fyi/grid/calculator">Utopia fluid grid calculator</a> which helps you to define a layout grid by clicking a few buttons.</q></p>
<p>Breaking content out of a central column is by now a classic CSS pattern, the latest and most robust iteration of which makes use of CSS Grid. A detailed write-up by Joshua Comeau: <a href="https://www.joshwcomeau.com/css/full-bleed/">Full-Bleed Layout Using CSS Grid</a>.</p>
<p>Speaking of classic CSS patterns, James Edwards thoroughly breaks down <a href="https://www.tpgi.com/the-anatomy-of-visually-hidden/">the anatomy of <code>.visually-hidden</code></a>.</p>
<p>Speaking of thoroughly breaking down, Manuel Matuzović walks us through <a href="https://web.dev/website-navigation/">building the main navigation for a website</a>. An interesting tidbit, as pointed out by Kitty Giraudel, is using the <code><template></code> element for easy <a href="https://kittygiraudel.com/2022/09/30/templating-in-html/">templating in HTML</a>. (Even easier if one could somehow use <code><slot></code> to fill in the blanks, if you ask me!)</p>
<p>While the <code><details></code> and <code><summary></code> elements invite use for all sorts of different interactivity needs (e.g. <a href="https://iamkate.com/code/tree-views/">tree views</a>), Scott O'Hara documents some subleties in their support and behavior that may steer you towards custom widgets for some use cases: <a href="https://www.scottohara.me//blog/2022/09/12/details-summary.html">The details and summary elements, again</a>.</p>
<p>In a decisively more mathy area, <a href="https://www.youtube.com/watch?v=jvPPXbo87ds">The continuity of splines</a> is a wide-ranging visual explanation by Freya Holmér (previously, <a href="https://www.youtube.com/watch?v=aVwxzDHniEw">The beauty of Bézier curves</a>).</p>
<p>Raph Levien proposes <q>a significantly better solution to the parallel curve problem than the current state of the art. It is accurate, robust, and fast. It should be suitable to implement in interactive vector graphics applications, font compilation pipelines, and other contexts</q>: <a href="https://raphlinus.github.io/curves/2022/09/09/parallel-beziers.html">Parallel curves of cubic Béziers</a>.</p>
<p><a href="https://github.com/cpressey/Facts-about-State-Machines">Facts about State Machines</a>, a series of short musings by Chris Pressey. <q>The goal of this list of facts is not to teach you what state machines are or how to use them; there are plenty of other resources for that. Rather, the goal here is to motivate their usage and to highlight things about them that are frequently overlooked, but nonetheless relevant.</q></p>
<p><a href="https://madebyevan.com/algos/">Algorithms</a> by Evan Wallace, <q>a list of algorithms and data structures that I found interesting enough to prototype and useful enough to write about</q>, mostly around CRDTs.</p>
<p>Ink & Switch essays are always fascinating. <a href="https://www.inkandswitch.com/inkbase/">Inkbase: Programmable Ink</a> and <a href="https://www.inkandswitch.com/potluck/">Potluck: Dynamic documents as personal software</a> are two recent ones.</p>
<p><a href="https://simonwillison.net/2022/Nov/26/productivity/">Coping strategies for the serial project hoarder</a>, a talk by Simon Willison about staying on top of several projects with comprehensive documentation and automated tests.</p>
<h2>Tools & Resources</h2>
<p><a href="https://jackrusher.com/classic-ux/">Classic HCI demos</a>, a <q>collection of HCI demo videos produced during the golden age from 1983-2002</q>, curated by Jack Rusher from the <a href="https://www.youtube.com/@sigchi">ACM SIGCHI YouTube channel</a>.</p>
<p>Axel Rauschmayer's new book <a href="https://exploringjs.com/nodejs-shell-scripting/">Shell scripting with Node.js</a> goes into the nitty-gritty of building command-line tools with Node.js and publishing them as npm packages.</p>
<p><a href="https://livecodingbook.toplap.org/">Live Coding: A User's Manual</a> by Alan F. Blackwell, Emma Cocker, Geoff Cox, Alex McLean, and Thor Magnusson <q>provides a practice-focused account of the origins, aspirations, and evolution of live coding, including expositions from a wide range of live coding practitioners.</q></p>
<p>The 4th edition of Erik Spiekermann's classic <em>Stop stealing sheep & find out how type works</em> has been released as a Creative-Commons-licensed PDF on the <a href="https://fonts.google.com/knowledge">Google Fonts Knowledge</a> page.</p>
<p><a href="https://github.com/mona-sans">Mona Sans and Hubot Sans</a> are a pair of variable fonts from GitHub, designed with Deni Anggara of <a href="https://www.degarism.com/">Degarism Studio</a> and released under the SIL Open Font License.</p>
<p>Gaël Poupard's <a href="https://ffoodd.github.io/chaarts/index.html">chaarts</a> probes how far you can take data visualization with just HTML + CSS. <q>Every chart in this project relies solely on semantic markup — <code><table></code> based — and a spread of CSS variables carried by the tags. No JavaScript required for display, and styles are progressively enhanced depending on your browser's capabilities.</q></p>
<p><a href="https://lightningcss.dev/">Lightning CSS</a>, <q>an extremely fast CSS parser, transformer, bundler, and minifier</q> written in Rust by Devon Govett.</p>
wsf-xxxv2022-08-21T00:00:00Zhttps://danburzo.ro/watchstarfork/2022-08-21/<h2>News</h2>
<p>Astro, the <em>zero JS by default</em> framework for building content-first websites (which is safe to say is most websites), has reached its <a href="https://astro.build/blog/astro-1/">1.0 release</a>. Jeff Delaney blitzes through Astro's features in a <a href="https://www.youtube.com/watch?v=gxBkghlglTg">tightly woven 3-minute video</a>.</p>
<p><a href="https://marijnhaverbeke.nl/blog/codemirror-6.html">CodeMirror 6.0</a> is <q>a from-scratch implementation based on the experience of building and maintaining versions 1 to 5 for the past 13 years. It aims to be more extensible and accessible than previous versions.</q></p>
<h2>Articles</h2>
<h3>HTML 🤝 CSS</h3>
<p>As I'm slowly making my way towards marking up a whole collection of <a href="https://danburzo.ro/digitizing-books/">traditionally typeset treatises</a> myself, I am very sympathetic to Eric A. Meyer's experiments with reproducing some aspects of the printed page on the web. There are three posts so far, on <a href="https://meyerweb.com/eric/thoughts/2022/04/26/flexibly-centering-an-element-with-side-aligned-content/">Flexibly Centering an Element with Side-Aligned Content</a>, <a href="https://meyerweb.com/eric/thoughts/2022/08/09/recreating-the-effects-of-nuclear-weapons-for-the-web/">Recreating “The Effects of Nuclear Weapons” for the Web</a>, and <a href="https://meyerweb.com/eric/thoughts/2022/08/15/table-column-alignment-with-variable-transforms/">Table Column Alignment with Variable Transforms</a>.</p>
<p><a href="https://lea.verou.me/2022/07/button-group/">What is the best way to mark up an exclusive button group?</a>, asks Lea Verou. A group of radio inputs seems to offer a lot out of the box, but <code><button></code>s and JavaScript better convey the intent. Léonie Watson expands on the thought in <a href="https://tink.uk/perceived-affordances-and-the-functionality-mismatch/">Perceived affordances and the functionality mismatch</a>.</p>
<p><a href="https://www.joshwcomeau.com/css/understanding-layout-algorithms/">Understanding Layout Algorithms</a>, Josh W. Comeau on building an intuition for CSS layout. Speaking of which, I'll have to sleep over what I've just read here, but it seems like very powerful stuff: <a href="https://css-tricks.com/exploring-css-grids-implicit-grid-and-auto-placement-powers/">Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers</a> by Temani Afif.</p>
<p>In <a href="https://www.tpgi.com/the-ballad-of-text-overflow/">The Ballad of Text Overflow</a>, James Edwards makes the case that the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow"><code>text-overflow</code></a> CSS property should never be used.</p>
<p><a href="https://web.dev/custom-properties-web-components/">How Nordhealth uses Custom Properties in Web Components</a>, David Darnes on using custom CSS properties as an interface to the Shadow DOM.</p>
<p>Bramus has put together a compendium of ways to <a href="https://web.dev/css-border-animations/">animate CSS borders</a>, complete with illuminating visuals.</p>
<h3>JavaScript, DOM</h3>
<p>Tom MacWright writes about <a href="https://macwright.com/2022/07/11/activation.html">Activation</a>, and how some web APIs that browsers only allow as a result of <a href="https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation">user activation</a> are not straightforward to invoke in asynchronous scenarios.</p>
<p>Part of a series on Node.js shell scripting, Axel Rauschmayer wrote a guide to <a href="https://2ality.com/2022/08/node-util-parseargs.html">parsing command line arguments with <code>util.parseArgs()</code></a>, a function introduced in Node 18.3.</p>
<h3>CMS and static sites</h3>
<p>Dana Byerly with some considerations on using WordPress as a CMS for an Eleventy website: <a href="https://danabyerly.com/articles/wordpress-and-eleventy-part-one-wordpress/">Part one: WordPress</a>, <a href="https://danabyerly.com/articles/wordpress-and-eleventy-part-two-eleventy/">Part two: Eleventy</a>.</p>
<p><a href="https://ben.balter.com/2022/06/30/helpful-404s-for-jekyll-and-github-pages/">Helpful 404s for Jekyll (and GitHub Pages)</a>, a clever trick by Ben Balter for your 404s: fetch <code>sitemap.xml</code> and suggest the most similar URL based its <a href="https://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance</a> to the missing one.</p>
<h3>Engineering</h3>
<p><a href="https://surma.dev/things/cost-of-convenience/">The cost of convenience</a>, Surma on designing APIs and the tradeoffs any abstraction introduces. <q>Every abstraction should ideally be optional (opt-in or opt-out) and come with escape hatches. If possible, it should also expose the abstractions below the top layer, so developers are in control and can help themselves.</q></p>
<p>The tutorial I wish I had when I started working with the command line: <a href="https://www.joshwcomeau.com/javascript/terminal-for-js-devs/">The Front-End Developer's Guide to the Terminal</a> by Josh W. Comeau. <em>And still</em> I got to learn a few new keyboard shortcuts!</p>
<h3>Security</h3>
<p><strong>Jabs at IABs.</strong> In <a href="https://krausefx.com/blog/ios-privacy-instagram-and-facebook-can-track-anything-you-do-on-any-website-in-their-in-app-browser">iOS Privacy: Instagram and Facebook can track anything you do on any website in their in-app browser</a> Felix Krause shines a light on questionable use of custom, in-app web browsers. Adrian Holovaty: <q>The more I think about it, the more I cannot believe <em>webviews with unfettered JavaScript access to third-party websites</em> ever became a legitimate, accepted technology. It’s bad for users, and it’s bad for websites.</q> His proposal? <a href="https://www.holovaty.com/writing/framebust-native-apps/">Let websites framebust out of native apps</a>. <q>If a website is loaded into a webview, and the website includes the appropriate <code>X-Frame-Options</code> header, the mobile OS should immediately stop loading the webview and open the URL in the user’s preferred web browser</q>. Note that non-hostile apps can choose privacy-protecting alternatives for opening third-party URLs on both iOS (<code>SFSafariViewController</code>) and Android (Chrome Custom Tabs) that still feel seamless to the user, and <a href="https://krausefx.com/blog/announcing-inappbrowsercom-see-what-javascript-commands-get-executed-in-an-in-app-browser">many apps already do</a>.</p>
<h2>Tools and resources</h2>
<p>Some new-to-me developments in interop for rich-text/block editors: the Block Protocol <a href="https://www.joelonsoftware.com/2022/01/27/making-the-web-better-with-blocks/">introduced by Jared Spolsky</a> earlier this year and Sanity's <a href="https://www.sanity.io/guides/introduction-to-portable-text">Portable Text</a> specification. I'm reminded of Condé Nast's <a href="https://github.com/condenast/atjson">atjson</a>.</p>
<p>Scott O'Hara has helpfully catalogued his <a href="https://github.com/scottaohara/accessible_components">thorough implementations</a> for various accessible components. <q>I've built a good handful of accessible markup patterns and widgets at this point. Each is based on testing with users, UX and design needs of past projects, and from following W3C specifications & notes.</q></p>
<p><a href="https://www.maban.co.uk/projects/product-planning-prompt-pack/">Product Planning Prompt Pack</a>, <q>a planning tool for teams that build features</q> by Anna Debenham.</p>
<p><a href="https://carlos.bueno.org/optimization/">The Mature Optimization Handbook</a>, a booklet by Carlos Bueno: <q>Performance optimization is, or should be, a cost/benefit decision. It’s made in the same way you decide just how much effort to put into other cross-cutting aspects of a system like security and testing. There is such a thing as too much testing, too much refactoring, too much of anything good.</q> (via <a href="https://www.placemark.io/post/engineering-round-up-optimization">Tom MacWright</a>).</p>
<p><a href="https://webhooks.fyi/">Webhook.fyi</a>, best practices for providing and consuming webhooks.</p>
<p><a href="https://colorandcontrast.com/#/">An interactive guide to color & contrast</a>, <q>a comprehensive guide for exploring and learning about the theory, science, and perception of color and contrast</q> by Nate Baldwin.</p>
<p>Eighteen fascinating <a href="https://www.youtube.com/playlist?app=desktop&list=PL7ddpXYvFXspUN0N-gObF1GXoCA-DA-7i">Lectures on Digital Photography</a> by Marc Levoy, with an <a href="https://sites.google.com/site/marclevoylectures/applets/gamma-correction">accompanying website</a> (via <a href="https://twitter.com/fvsch/status/1558885641731153923">Florens Verschelde</a>).</p>
<p><strong>Hypertext experiments.</strong> <a href="https://ncase.me/nutshell/">Nutshell</a>, expandable explanations by Nicky Case inspired by Ted Nelson's StretchText. I've also found the mechanics on <a href="https://notes.andymatuschak.org/">Andy Matuschak's notes</a> interesting.</p>
<h2>Today I learned</h2>
<p><strong><code>wget</code> outta here.</strong> Stefan Judis recently <a href="https://twitter.com/stefanjudis/status/1559430734003175427">asked</a> about tools for finding broken outbound links on your website. With <code>wget</code> being pretty good for finding internal 404s (<code>wget --spider --recursive --level 0 yourwebsite.com</code>), might it work on external links as well? A resounding <em>sort of</em>, a bit too caveaty to recommend as the definitive solution. Not really a one-liner either, but you can find it on my <a href="https://danburzo.ro/toolbox/wget/"><code>wget</code> recipes</a> page. <a href="https://rocket.modern-web.dev/tools/check-html-links/overview/"><code>check-html-links</code></a> by Thomas Allmer seems to be a better choice, with support for external links coming imminently.</p>
<hr />
<p><strong>A beautiful typeface.</strong> Ploquine, a family by Emma Marichal inspired by wooden type, is the winner of the <a href="https://www.type-together.com/2022-gerard-unger-scholarship-results">2022 Gerard Unger Scholarship</a>. So so good. <a href="http://postdiplome.esad-amiens.fr/ploquine/">The author's dissertation</a> is available as a PDF for insights on the design process.</p>
<p><strong>A revived typeface.</strong> Speaking of design process, <a href="https://www.designingtyperevivals.com/deaetna.html">De Aetna</a> is an open (OFL) typeface released by Riccardo Olocco and Michele Patanè as a companion to their handbook <em>Designing type revivals</em>.</p>
<p><strong>A parting thought</strong> until next time:</p>
<blockquote>
<p>Bookmarking isn't about reading something later, it's about not reading it now — <a href="https://twitter.com/ReinH/status/1557594477929254912">@ReinH</a></p>
</blockquote>
wsf-xxxiv2022-08-07T00:00:00Zhttps://danburzo.ro/watchstarfork/2022-08-07/<h2>News</h2>
<p>Node 18 gets us <a href="https://nodejs.org/en/blog/announcements/v18-release-announce/">new features</a> for which external packages were previously needed: a built-in <a href="https://nodejs.org/api/globals.html#fetch"><code>fetch()</code></a> function could replace <a href="https://github.com/node-fetch/node-fetch"><code>node-fetch</code></a>, and the <a href="https://nodejs.org/dist/latest-v18.x/docs/api/test.html"><code>node:test</code></a> module has an API similar to <a href="https://github.com/substack/tape"><code>tape</code></a>. Node 18.3 also debuted the <a href="https://nodejs.org/api/util.html#utilparseargsconfig"><code>util.parseArgs</code></a> command-line argument parser, which is being iterated on at <a href="https://github.com/pkgjs/parseargs">pkgjs/parseargs</a>; in the meantime <a href="https://github.com/danburzo/opsh">opsh</a> will do.</p>
<p>The <a href="https://www.w3.org/WAI/ARIA/apg/">ARIA Authoring Practices Guide</a> got an excellent makeover by <a href="https://bocoup.com/blog/redesigning-aria-apg">Bocoup</a>.</p>
<p>A neat little update in Firefox Dev Tools lets you <a href="https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Debug_Scrollable_Overflow">debug scrollable overflow</a>. I used to rely on this JavaScript snippet to identify the elements that cause horizontal overflow:</p>
<pre class="language-js"><code class="language-js">Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><br /> <span class="token parameter">el</span> <span class="token operator">=></span> el<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>right <span class="token operator">></span> window<span class="token punctuation">.</span>innerWidth<br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Articles</h2>
<h3>The good markup</h3>
<p>With <a href="https://www.tpgi.com/subheadings-subtitles-alternative-titles-and-taglines-in-html/">Subheadings, subtitles, alternative titles and taglines in HTML</a> Steve Faulkner offers a conclusive pattern for the common dilemma, using a heading and adjacent paragraphs, all wrapped in an <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"><code><hgroup></code></a>. Still on the topic, Patrick H. Lauke demonstrates <a href="https://twitter.com/patrick_h_lauke/status/1555984227514155008">a small hack</a> for making VoiceOver play nice with headings made up of several <code><span></code> elements.</p>
<p><a href="https://css-tricks.com/write-html-the-html-way-not-the-xhtml-way/">Write HTML, the HTML Way (Not the XHTML Way)</a>, says Jens Oliver Meiert. I still can't omit closing tags without a pang of guilt, but admittedly these tips make writing HTML much less annoying.</p>
<p><a href="https://kittygiraudel.com/2022/04/02/accessible-cards/">Accessible Cards</a>, Kitty Giraudel's take on whole-area tappable cards, with useful links to prior efforts.</p>
<h3>CSS</h3>
<p><a href="https://css-irl.info/aspect-ratio-is-great/">Aspect Ratio is Great</a>: Michelle Barker outlines a few real-life scenarios where the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio"><code>aspect-ratio</code></a> CSS property is handy.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has"><code>:has()</code></a> CSS selector opens up a whole world of possibilities, and it feels we're still barely scratching the surface. Matthias Ott explores a few of them in <a href="https://matthiasott.com/notes/css-has-a-parent-selector-now">CSS :has() A Parent Selector Now</a>.</p>
<p>With the inclusion of wide color gamuts in CSS Color 4, Chris Lilley (one of the specification's editors) takes the opportunity to explain <a href="https://svgees.us/blog/whatGamuts.html">what color gamuts are</a>. Chris and Lea Verou (who co-edits the spec) have also recently cut the initial release of their library <a href="https://svgees.us/blog/colorjs-release.html">Color.js</a>.</p>
<p><a href="https://ericwbailey.design/writing/all-the-user-facing-states/">All the user-facing states</a>, a useful reference maintained by Eric Bailey.</p>
<h3>Web Components</h3>
<p>For web components to be resilient to JavaScript issues, they generally need to wrap or extend standard HTML elements. Paul Hebert writes about this in <a href="https://cloudfour.com/thinks/web-components-as-progressive-enhancement/">Web Components as Progressive Enhancement</a>. Although extending built-in elements is still not supported in Safari, a <a href="https://github.com/ungap/custom-elements">tiny polyfill</a> is available.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals"><code>ElementInternals</code></a> API will make it easier to have custom elements participate in forms like standard form elements. Maggie Wachs explores what to do in the meantime in <a href="https://www.filamentgroup.com/lab/forms-with-custom-elements/">Building forms with custom elements</a>.</p>
<p>David Darnes has written an in-depth guide to <a href="https://darn.es/building-tabs-in-web-components/">Building tabs in Web Components</a>.</p>
<p><a href="https://www.jackfranklin.co.uk/blog/working-with-react-and-the-web-platform/">Why I don't miss React: a story about using the platform</a> is Jack Franklin's experience moving from a React-heavy gig to the Web Components-enthused Chrome Dev Tools.</p>
<h3>JavaScript</h3>
<p>Not to say it's an outright mystery how users manage to work with npm-based tools on Windows outside of <a href="https://docs.microsoft.com/en-us/windows/wsl/about">WSL</a>, but in my own command-line tools it has been a niche concern, of the <em>follow Node conventions and hope it works</em> kind. Axel Rauschmayer's deep-dive on <a href="https://2ality.com/2022/07/nodejs-path.html">Working with file system paths on Node.js</a> is going to come in handy the next time I have to understand some of the cross-platform gotchas.</p>
<p>In <a href="https://infrequently.org/2021/03/reactive-data-modern-js/">Reactive Data With Modern JavaScript</a>, Alex Russell shows a succinct way to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget"><code>EventTarget</code></a> and JavaScript Proxies to have UIs react to data changes.</p>
<p>Adrian Cooney shares a brilliant technique for scraping <code><div></code> soups: just grab the data from the browser's memory! Read about the principle, and the dedicated CLI tool, in <a href="https://www.adriancooney.ie/blog/web-scraping-via-javascript-heap-snapshots">Web Scraping via JavaScript Runtime Heap Snapshots</a>. For websites that are <em>not</em> hostile to scraping, there's always <a href="https://github.com/danburzo/hred">hred</a>.</p>
<h3>Performance</h3>
<p>Nolan Lawson has been writing about SPAs (single-page applications) and how they stack up against MPAs (multiple-plage applications). The latest installment, <a href="https://nolanlawson.com/2022/06/27/spas-theory-versus-practice/">SPAs: theory versus practice</a>, proposes that <q>the core of the debate can be summed up by these truisms: 1. The best SPA is better than the best MPA. 2. The average SPA is worse than the average MPA.</q></p>
<p>Relatedly: <a href="https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na">Making the world’s fastest website, and other mistakes</a>, a five-part tour de force / emotional rollercoaster by Taylor Hunt.</p>
<p>In <a href="https://blog.nelhage.com/post/reflections-on-performance/">Reflections on software performance</a>, Nelson Elhage has <q>come to believe that the “performance last” model will rarely, if ever, produce truly fast software (and, as discussed above, I believe truly-fast software is a worthwhile target)</q>, and goes on to explain why that's the case.</p>
<p>When optimizing web fonts, I've always thought the term <em>subsetting</em> risks giving off the wrong idea and that something like <em>splitting</em> would be clearer. Browsers are smart enough that there isn't a good reason to not make available all the glyphs in a font to text that needs it. Paul Hebert outlines two common techniques in <a href="https://cloudfour.com/thinks/font-subsetting-strategies-content-based-vs-alphabetical/">Font subsetting strategies: content-based vs. alphabetical</a> but favors splitting the font into alphabets. Richard Rutter shows how to install and use <code>fonttools</code> for the purpose in <a href="https://clagnut.com/blog/2418/">How to subset a variable font</a>.</p>
<h3>Making things</h3>
<p><a href="https://riffle.systems/essays/prelude/">Building data-centric apps with a reactive relational database</a> by Nicholas Schiefer, Geoffrey Litt, Johannes Schickling, and Daniel Jackson: <q>We’re exploring a new way to manage data in apps by storing all app state — including the state of the UI — in a single reactive database. Instead of imperatively fetching data from the database, the user writes reactive queries that update with fresh results whenever their dependencies change.</q> Still on the research front, I'll be chewing on this Ink & Switch essay for a while: <a href="https://www.inkandswitch.com/crosscut/">Crosscut: Drawing Dynamic Models</a>.</p>
<p>Shuvomoy Das Gupta has sourced some nuggets from various interviews with the computer scientist in <a href="https://shuvomoy.github.io/blogs/posts/Knuth-on-work-habits-and-problem-solving-and-happiness/">Donald Knuth on work habits, problem solving, and happiness</a>.</p>
<p>Robin Sloan proposes <a href="https://www.robinsloan.com/lab/specifying-spring-83/">Spring '83</a>, a protocol based on little HTML + CSS vignettes (no images, no JavaScript).</p>
<p>Wouter Groeneveld catalogs all the <a href="https://brainbaking.com/post/2022/04/cool-things-people-do-with-their-blogs/">Cool Things People Do With Their Blogs</a>.</p>
<p>In causal news, concerted efforts towards de-emphasizing the concept of files and the filesystem has made kids-these-days <a href="https://www.theverge.com/22684730/students-file-folder-directory-structure-education-gen-z">oblivious to their existence</a>. Scott Jenson builds the case for files in <a href="https://jenson.org/files/">The future needs files</a>, and the follow-up <a href="https://jenson.org/files2/">The present needs files</a>.</p>
<p><a href="https://www.derekyu.com/makegames/">Make games with Derek</a> Yu, maker of Spelunky.</p>
<h2>Tools</h2>
<p>Facebook's <a href="https://github.com/facebook/lexical">Lexical</a> is a fresh attempt to tackle the hellhole that is extensible rich-text editing on the web. The company's previous framework called <a href="https://github.com/facebook/draft-js">Draft</a> has been shelved in the process.</p>
<p>Untrusted <code>.svg</code> files are safe to serve in HTML <code><img></code> elements, but they can be unsafe to access directly. Currently an early release, <a href="https://github.com/cloudflare/svg-hush"><code>svg-hush</code></a> aims to <q>make arbitrary SVG files as benign and safe to serve as images in other common Web file formats</q>.</p>
<p><a href="https://github.com/shuding/tilg"><code>tilg</code></a> (tiny logger) by Shu Ding is the spiritual successor to <code>why-did-you-update</code> for debugging React components.</p>
<p><a href="https://github.com/placemark/flat-drop-files"><code>flat-drop-files</code></a> by Tom MacWright makes working with dragged-n-dropped or pasted files easier by flattening nested folder structures, omitting junk files, and more.</p>
<h2>Today I Learned</h2>
<p><strong>Change the tab width in GitHub</strong>: I didn't know there's <a href="https://github.com/settings/appearance">a preference</a> in GitHub that controls how much space tab characters take — basically the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size"><code>tab-size</code></a> CSS property. As we use tabs troughout our codebases, having them narrower makes reviewing PRs <em>much</em> better (h/t <a href="https://twitter.com/JamesCoyle95/status/1553664669793099776">James Coyle</a>).</p>
<h2>Media</h2>
<p><strong>A browser game.</strong> <a href="https://wikitrivia.tomjwatson.com/">Wikitrivia</a> has you arranging historical events on a timeline. The data is fetched from Wikimedia APIs, so the events are delightfully all over the place.</p>
<p><strong>A documentary.</strong> The upcoming <a href="https://www.graphicmeans.com/">Graphic Means</a> <q>explores graphic design production of the 1950s through the 1990s — from linecaster to photocomposition, and from paste-up to PDF.</q></p>
<p><strong>A couple of fun interactive maps.</strong> <a href="https://chronotrains-eu.vercel.app/">How far can you go by train in 5h?</a> by Benjamin Td <q>shows you how far you can travel from each station in Europe in less than 5 hours</q>. <a href="https://tjukanovt.github.io/notable-people">Notable people</a> by Topi Tjukanov shows the most famous person born in any place around the globe.</p>
<hr />
<p>Heavy rotation: <a href="https://constantsmiles.bandcamp.com/album/paragons-2">Constant Smiles — Paragons</a></p>
wsf-xxxiii2022-01-01T00:00:00Zhttps://danburzo.ro/watchstarfork/2022-01-01/<h2>News</h2>
<p><a href="https://www.w3.org/TR/css-2021/">CSS Snapshot 2021</a> has been released. It <q>collects together into one definition all the specs that together form the current state of Cascading Style Sheets (CSS) as of 2021.</q></p>
<p>Safari has been maintaining its newfound momentum with the release of Safari 15.2, which supports <a href="https://webkit.org/blog/12058/wide-gamut-2d-graphics-using-html-canvas/">wide gamut color on <code><canvas></code></a>. While Chromium 94+ browsers have the <code>display-p3</code> color space available, Safari 15.2 also adds the <code>rec2020</code> color space in the mix. And since Safari can handle CSS Level 4 colors such as <code>color(rec2020 1 1 0)</code>, it's much easier to draw on a wide-gamut canvas. See the full <a href="https://developer.apple.com/documentation/safari-release-notes/safari-15_2-release-notes">Safari 15.2 release notes</a>.</p>
<p>More exciting things are lined up in <a href="https://webkit.org/blog/12156/release-notes-for-safari-technology-preview-137/">Safari TP 137</a>:</p>
<ul>
<li>the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has"><code>:has()</code> pseudo-class</a>, informally but sort of inaccurately known as the <em>parent selector</em>, has been enabled by default.</li>
<li>new color spaces have been added: <code>color(srgb-linear)</code>, <code>color(xyz-d50)</code>, <code>color(xyz-d65)</code>, <a href="https://bottosson.github.io/posts/oklab/"><code>oklab()</code> and <code>oklch()</code></a>.</li>
<li>one of the two Safari bugs that complicated <a href="http://localhost:8080/media-query-units/">my recommendations for media queries units</a> has been fixed: <code>rem</code> in media queries are now calculated using <code>font-size: initial</code>, not the <code>html</code> element font-size.</li>
</ul>
<h2>Articles</h2>
<h3>Interaction Design, Accessibility</h3>
<p><a href="https://dougengelbart.org/content/view/374/">An interactive version</a> of Doug Engelbart's 1968 demo by Christina Engelbart and Bret Victor.</p>
<p><a href="https://adrianroselli.com/2021/12/under-engineered-dependency-questions.html">Under-Engineered Dependency Questions</a> by Adrian Roselli. <q>A common interface pattern allows users to choose one item from a pre-defined set of choices, while still allowing them to add a custom selection if nothing else fits. Often these are radio buttons with one choice labeled “Other” that makes another field visible.</q></p>
<p><a href="https://brucelawson.co.uk/2021/component-libraries-accessibility-and-transparency/">Component libraries, accessibility and transparency</a>, Bruce Lawson on evaluating component libraries' accessibility claims.</p>
<p><a href="https://web.dev/building-a-toast-component/">Building a toast component</a> by Adam Argyle. <q>Toasts are non-interactive, passive, and asynchronous short messages for users. Generally they are used as an interface feedback pattern for informing the user about the results of an action.</q></p>
<p>Bramus's answer to the question of <q>one thing that people can do to make their website better</q> is to <a href="https://css-tricks.com/embrace-the-platform/">Embrace the Platform</a>.</p>
<h3>CSS</h3>
<p><a href="https://css-tricks.com/standardizing-focus-styles-with-css-custom-properties/">Standardizing Focus Styles With CSS Custom Properties</a> by Stephanie Eckles. <q>We’re going to look at a technique to make your focus styles more manageable across your project by using CSS custom properties and learn about a modern CSS focus selector.</q></p>
<p>Two talks by Rachel Andrew: <a href="https://2021.stateofthebrowser.com/speakers/rachel-andrew/">CSS Layout from the inside out</a> at State of the Browser and <a href="https://www.youtube.com/watch?v=yMEjLBKyvEg">The fundamentals of CSS layout</a> at the Chrome Dev Summit.</p>
<p><q>Oftentimes, we wish that there was a way to avoid a certain CSS issue or behaviors from happening. You know, content is dynamic, and things can change on a web page, thus increasing the possibility of a CSS issue or a weird behavior.</q> <a href="https://ishadeed.com/article/defensive-css/">Defensive CSS</a> by Ahmad Shadeed is a collection of snippets to mitigate that.</p>
<p><a href="https://alistapart.com/article/breaking-out-of-the-box/">Breaking Out of the Box</a> by Patrick Brosset. <q>CSS is about styling boxes. In fact, the whole web is made of boxes, from the browser viewport to elements on a page. But every once in a while a new feature comes along that makes us rethink our design approach.</q></p>
<h3>Performance</h3>
<p><a href="https://nolanlawson.com/2021/12/17/introducing-fuite-a-tool-for-finding-memory-leaks-in-web-apps/">Introducing <code>fuite</code>: a tool for finding memory leaks in web apps</a> by Nolan Lawson. It's a CLI tool built on top of Puppeteer that lets you run scenarios against a SPA, such as clicking on internal links then hitting the back button, and reports back any suspicious growth in memory usage.</p>
<p>In <a href="https://blogs.windows.com/msedgedev/2021/12/09/debug-memory-leaks-detached-elements-tool-devtools/">Debug memory leaks with the Microsoft Edge Detached Elements tool</a>, Patrick Brosset demonstrates the main features of the Dev Tools panel available in Edge 97.</p>
<p><a href="https://calendar.perfplanet.com/2021/a-unified-theory-of-web-performance/">A Unified Theory of Web Performance</a> by Alex Russell:</p>
<blockquote>
And so we have the three parts of a uniform mission or theory of web performance:
<ol>
<li>The mission of web performance is to expand access to information and services on the web.</li>
<li>We do this by reducing latency and variance across all interactions in a user’s session to return the system to an interactive state more reliably.</li>
<li>To accomplish this, we work to improve the tail of the distribution because that is how we make systems accessible, reliable, and equitable.</li>
</ol>
</blockquote>
<h3>Languages, algorithms, data structures</h3>
<p><a href="https://github.com/yoshuawuyts/rust-for-js-peeps">Rust for JavaScript peeps</a>, a chill guide by Yoshua Wuyts.</p>
<p><a href="https://www.notion.so/blog/data-model-behind-notion">The data model behind Notion's flexibility</a> by Jake Teton-Landis: it's blocks all the way down.</p>
<p>Regarding <a href="https://statecharts.dev/">statecharts</a>: Florens Verschelde walks us through <a href="https://fvsch.com/learning-xstate">Learning XState by refactoring an old project</a>. Using XState and React, Andy Jakubowski has built <a href="https://github.com/andyjakubowski/statechart-watch">a replica</a> of the Citizen Quartz Multi Alarm III watch, based on David Harel's 1987 paper that introduced statecharts.</p>
<p><a href="https://www.mapbox.com/blog/adaptive-projections">Reimagining projections for the interactive maps era</a>, Vladimir Agafonkin on the complexities of making adaptive projections, <q>a novel way to make interactive maps more accurate on a global scale, without any compromises to user experience, rendering quality or street-level precision</q>.</p>
<h3>Making software</h3>
<p><a href="https://thesephist.com/posts/research-community/">Towards a research community for better thinking tools</a>, a <q>loosely structured collection of different building blocks from which I think we can build a good research community to push this space forward</q> by Linus.</p>
<p><a href="https://www.baldurbjarnason.com/2021/making-colophon-cards/">Making Colophon Cards</a>: Baldur Bjarnason has been <q>documenting the process, the design, development, research, marketing, discussion with my advisers, and other details</q> of his <q>specific take on the note-oriented personal information manager</q>.</p>
<p><a href="https://twitter.com/dan_abramov/status/1470613731071696896">A hundred things I learned working on the React team</a>, a Twitter mega-thread by Dan Abramov.</p>
<h3>Graphics</h3>
<p>In <a href="https://tympanus.net/codrops/2021/12/07/coloring-with-code-a-programmatic-approach-to-design/">Coloring With Code — A Programmatic Approach To Design</a>, George Francis shows you how to use <a href="https://culorijs.org/">culori</a> to <q>create beautiful, inspiring, and unique color palettes/combinations, all from the comfort of your favorite text editor!</q></p>
<p>I've been a big fan of Marc Edward's <a href="https://bjango.com/articles/iconspeedruns/">vector icon speedruns</a> but I couldn't always tell what's going on. <a href="https://bjango.com/articles/speedruncamerairis/">Camera iris icon speedrun</a> <q>aims to be a director’s commentary for my camera iris icon speedrun, noting the techniques used, and why they were chosen.</q></p>
<p><a href="https://observablehq.com/@daformat/rounding-polygon-corners">Rounding polygon corners</a>, an interactive notebook by Mathieu Jouhet.</p>
<h2>Books & Resources</h2>
<p>The second edition of the <a href="https://datascienceatthecommandline.com/2e/">Data Science at the Command Line</a> book has just been released.</p>
<p><a href="https://www.patterns.dev/">Patterns.dev</a>, a <q>free book on design patterns and component patterns for building powerful web apps with vanilla JavaScript and React</q> by Lydia Hallie and Addy Osmani.</p>
<p><a href="https://designing-design-tools.nolwennmaudet.com/">Designing Design Tools</a>, Nolwenn Maudet's dissertation investigating the questions: <q>How do designers work with design software? And how can we design novel design tools that better support designer practices?</q> (via <a href="https://twitter.com/impactology/status/1463764355883798535?s=20">Raghav Agrawal</a>).</p>
<p><a href="https://typographica.org/on-typography/now-open-the-typographica-library/">Now Open: the Typographica Library</a>, <q>a digital bookshelf of type and lettering resources to aid research and selection.</q></p>
<p><a href="https://fonts.google.com/knowledge">Google Fonts Knowledge</a>, a <q>library of original guides to the world of typography, which the Google Fonts team is producing in collaboration with typographic experts from around the world.</q></p>
<h2>Tools</h2>
<p><a href="https://open-props.style/">Open Props</a>, a CSS system based on custom properties by Adam Argyle.</p>
<p><a href="https://www.solidjs.com/">SolidJS</a>, <q>simple and performant reactivity for building user interfaces.</q></p>
<p><a href="https://github.com/floating-ui/floating-ui/">floating-ui</a>, a <q>powerful toolkit to position floating elements while intelligently keeping them in view.</q></p>
<p><a href="https://servefolder.dev/">Serve folder for web development</a>: <q>This page lets you host a local folder with web development files, such as HTML, JavaScript and CSS, directly in your browser. It works using Service Workers: everything is served from your local system only, nothing is uploaded to a server, and your files are not shared with anybody else.</q></p>
<hr />
<p><strong>A browser game.</strong> <a href="https://www.powerlanguage.co.uk/wordle/">Wordle</a>: guess the hidden word in six tries. There's a single puzzle each day, so no binge risk.</p>
wsf-xxxii2021-11-26T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-11-26/<h2>News</h2>
<p><a href="https://webkit.org/blog/12040/release-notes-for-safari-technology-preview-135/">Safari Technology Preview 135</a> was released with support for the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/accent-color"><code>accent-color</code></a> CSS property, and for lazy-loading images with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/loading"><code>loading=lazy</code></a> HTML attribute, in line with Firefox and Chromium-based browsers. It also premieres the <a href="https://www.bram.us/2021/07/08/the-large-small-and-dynamic-viewports/">new viewport units</a> that solve the <a href="https://danburzo.ro/on-safari-15/#new-tab-bar"><code>100vh</code> dilemma</a> on mobile.</p>
<h2>Articles</h2>
<h3>UX, Accessibility</h3>
<p><a href="https://www.nngroup.com/articles/growing-your-ux-career-study-guide/">Growing in Your UX Career</a>, a guide by Nielsen Norman Group. <q>Unsure where to start? Use this collection of links to articles, videos, and a free report for advice to grow in your user experience career.</q></p>
<p>The last two installments in Henny Swan's <em>Browsing with assistive technology</em> series: <a href="https://tetralogical.com/blog/2021/11/10/browsing-with-screen-magnification/">Browsing with screen magnification</a> and <a href="https://tetralogical.com/blog/2021/11/15/browsing-with-speech-recognition/">Browsing with speech recognition</a>.</p>
<p><a href="https://whistlr.info/2021/in-defence-of-dialog/">In Defence Of Dialog</a> has Sam Thorogood walk us through some interesting use-cases for the <code><dialog></code> element.</p>
<h3>CSS</h3>
<p><a href="https://www.joshwcomeau.com/css/custom-css-reset/">My Custom CSS Reset</a>, Josh W. Comeau's copiously annotated contribution to the time-honored tradition that is the Reset Stylesheet. I myself have been keeping <a href="https://github.com/jensimmons/cssremedy">cssremedy</a> at arm's length for the purpose.</p>
<p><a href="https://nemzes.net/posts/animating-height-auto/">Animating <code>height: auto</code></a>, a novel solution by Nelson Menezes to the classic head-scratcher using CSS Grid.</p>
<p><a href="https://jakearchibald.com/2021/dom-cross-fade/">Cross-fading any two DOM elements is currently impossible</a>, an interactive article in which Jake Archibald illustrates how alpha complicates the compositing of overlapped elements.</p>
<h3>Creative Coding</h3>
<p><em>Processing: the Software that Shaped Creative Coding</em>, <q>an oral history of the programming language that cracked the world of code for artists and designers</q> by Liz Stinson. Here's <a href="https://eyeondesign.aiga.org/processing-the-software-that-shaped-creative-coding/">Part I</a> and <a href="https://eyeondesign.aiga.org/an-oral-history-of-processing-part-two/">Part II</a>.</p>
<p><a href="https://css-tricks.com/creating-generative-patterns-with-the-css-paint-api/">Creating Generative Patterns with The CSS Paint API</a> by George Francis, on using JavaScript paint worklets to draw colorful geometric backgrounds.</p>
<p><a href="https://observablehq.com/@jobleonard/pseudo-blue-noise">Pseudo-Blue Noise</a>, an interactive notebook by Job van der Zwan describing a fast approximation of blue noise for image dithering.</p>
<h3>Software engineering</h3>
<p><a href="https://www.inkandswitch.com/peritext/">Peritext: A CRDT for Rich-Text Collaboration</a> by Ink & Switch. <q>We believe that CRDTs for rich text could enable new writing workflows, with powerful version control and support for both synchronous and asynchronous collaboration. In this work, we have shown that traditional plain-text CRDTs are not capable of preserving authors’ intent when merging edits to rich-text documents. We have developed a rich-text CRDT that supports overlapping inline formatting, and shown how to implement it efficiently.</q></p>
<p><a href="https://www.placemark.io/post/how-placemark-implements-undo-redo-to-make-map-making-safe-and-chill">How Placemark implements undo/redo to make map making safe and chill</a> by Tom MacWright. <q>Undo is a funny feature to implement, because it can be incredibly difficult to design and code, and once you're done users nod and say, 'oh, okay, it has undo.'</q></p>
<p><a href="https://rsms.me/etc/handmade-seattle-2021/">Quality in Software</a>, a presentation by Rasmus Andersson at Seattle Handmade 2021.</p>
<p><a href="https://neverworkintheory.org/2021/08/29/software-development-waste.html">Software Development Waste</a>, a summary of causes for <q>activity that produces no value for the customer or user</q> extracted from the <a href="https://doi.org/10.1109/icse.2017.20">eponymous paper</a>.</p>
<p><a href="https://web.eecs.utk.edu/~azh/blog/thisprojectwillonlytake.html">"This project will only take 2 hours"</a>, Austin Z. Henley on how even the simplest-sounding application reveals itself as complex once you spell out how you expect it to work.</p>
<p>A couple of quick programming language tours: <a href="https://fasterthanli.me/articles/a-half-hour-to-learn-rust">A half-hour to learn Rust</a> by Amos, which inspired <a href="https://gist.github.com/ityonemo/769532c2017ed9143f3571e5ac104e50">A half-hour to learn Zig</a> by Isaac Yonemoto (via <a href="https://simonwillison.net/2021/Nov/5/a-half-hour-to-learn-rust/">Simon Willison</a>, <a href="https://twitter.com/andrestaltz/status/1461781321446006792">André Staltz</a>). Really helpful for get a bit of a mental model going on, would love one for Go to round things off.</p>
<p>Jordan Scales uses a <a href="https://cards.jordanscales.com/toposort">Topological sort</a> algorithm to sort out the CSS z-indexes (z-indices?) of elements based on a series of <code>A above B</code> constraints.</p>
<p>Most of <a href="https://danburzo.ro/projects">my projects</a> are the result of yak shaving (some of which has been <a href="https://lobste.rs/s/ngswph/what_s_your_current_yak_shaving_depth">going on for a while now</a>), so I found myself nodding along Lea Verou's <a href="https://lea.verou.me/2021/11/on-yak-shaving-and-md-block-an-html-element-for-markdown/">On Yak Shaving and <code><md-block></code>, a new HTML element for Markdown</a>.</p>
<h2>Tools & Resources</h2>
<p>I was researching early Apple <a href="https://developer.apple.com/design/human-interface-guidelines/">Human Interface Guidelines</a> and stumbled upon <a href="https://guidebookgallery.org/index">GUIdebook</a>, which is just an incredible website <q>dedicated to preserving and showcasing Graphical User Interfaces, as well as various materials related to them</q>. It was built by Marcin Wichary almost twenty years ago.</p>
<p><a href="https://github.com/tldraw/tldraw">tldraw</a>, a <q>tiny little drawing app</q> by Steve Ruiz.</p>
<p><a href="https://www.joshwcomeau.com/css/introducing-shadow-palette-generator/">Shadow Palette Generator</a> by Josh W. Comeau, a tool to help you generate CSS <code>box-shadow</code> values.</p>
<p><a href="https://remix.run/">Remix</a>, a new React-based web framework, similar in purpose to <a href="https://nextjs.org/">Next.js</a> and <a href="https://astro.build/">Astro</a>.</p>
<p><a href="https://github.com/GoogleChromeLabs/container-query-polyfill">container-query-polyfill</a>, a small polyfill for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries">CSS Container Queries</a> by Surma.</p>
<p><a href="https://github.com/AsyncBanana/microdiff">microdiff</a>, <q>a fast, zero dependency object and array comparison library. Significantly faster than most other deep comparison libraries and has full TypeScript support.</q></p>
<p><a href="https://github.com/Orange-OpenSource/hurl">hurl</a>, a command-line tool built on top of <code>curl</code> that runs HTTP requests defined in a simple plain text format. <q>It can perform requests, capture values and evaluate queries on headers and body response.</q></p>
<h2>Media</h2>
<p><strong>Two lovely websites.</strong> <a href="https://gerardhoffnung.com/">An archive</a> of late artist Gerard Hoffnung's work, <q>an extensive collection of Hoffnung’s cartoons, his biography, rare film footage and audio and an extensive archive of material
and memorabilia</q>. <a href="https://www.women-in-type.com/">Women in Type</a>, <q>rediscovering women’s contribution to type history</q>.</p>
<p><strong>A casual game.</strong> <a href="https://holedown.com/">Holedown</a>, where you <q>dig deep underground by shooting balls and breaking blocks, traversing your way to the planet cores</q>, was pretty captivating. (via <a href="https://twitter.com/joshmillard/status/1458130126844039172">Josh Millard</a>)</p>
<p><strong>An infectious song.</strong> Alex Cameron as stylish as ever on his new single, <a href="https://www.youtube.com/watch?v=bT2eiag28wA">Sara Jo</a>.</p>
wsf-xxxi2021-11-06T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-11-06/<h2>News</h2>
<h3>Browsers & apps</h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/94">Firefox 94</a> released with support for <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/enterKeyHint">the <code>enterKeyHint</code> attribute</a>, which now has universal coverage across major browsers, and some groundwork for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer">cascade layers</a>.</p>
<p><a href="https://developer.chrome.com/blog/new-in-chrome-95/">Chrome 95</a> is out, with support for the <a href="https://wicg.github.io/eyedropper-api/">Eyedropper API</a> (read <a href="https://web.dev/eyedropper/">a write-up</a> by Patrick Brosset and Thomas Steiner) and <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLPattern"><code>URLPattern</code></a>. <a href="https://github.com/kenchris/urlpattern-polyfill">An <code>URLPattern</code> polyfill</a> is available if you want to start using the feature today.</p>
<p>Safari Technology Previews have been on a roll in recent months. Highlights from <a href="https://webkit.org/blog/12033/release-notes-for-safari-technology-preview-134/">Safari TP 134</a> include <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog">the <code><dialog></code> element</a>, support for color spaces in <code><canvas></code> (like in Chrome, <code>srgb</code> and <code>display-p3</code> are currently available), and cascade layers. On top of that, WebKit is getting a new <a href="https://blogs.igalia.com/nzimmermann/posts/2021-10-29-layer-based-svg-engine/">layer-based SVG engine</a>, developed by Igalia with backing from *checks notes* the makers of the Thermomix multicooker? Neat!</p>
<p>Adobe Photoshop and Illustrator are coming to the browser. <a href="https://web.dev/ps-on-the-web/">A write-up on web.dev</a> describes the technicall nitty-gritty that makes this possible. In the desktop app, Photoshop added <a href="https://helpx.adobe.com/photoshop/using/gradient-interpolation.html">perceptual gradient interpolation</a> in the <a href="https://bottosson.github.io/posts/oklab/">Oklab color space</a>, which is also being added to the <a href="https://drafts.csswg.org/css-color/#ok-lab">CSS Color Level 4 spec</a>.</p>
<h3>Learning & documentation</h3>
<p>During Chrome Dev Summit 2021 new learning paths were announced on web.dev:</p>
<ul>
<li><a href="https://web.dev/learn/design/">Learn Responsive Design</a> with Jeremy Keith;</li>
<li><a href="https://web.dev/learn/forms/">Learn Forms</a> with Michael Scharnagl; and</li>
<li><a href="https://web.dev/learn/pwa/">Learn PWA</a> with Maximiliano Firtman.</li>
</ul>
<p>And with the occasion of the PWA Summit, Microsoft have refreshed their own documentation: <a href="https://blogs.windows.com/msedgedev/2021/10/19/pwa-summit-learn-progressive-web-apps-documentation/">Learn to build great Progressive Web Apps</a>.</p>
<p>The highly-anticipated <a href="https://beta.reactjs.org/">React.js docs rewrite around hooks</a> is in beta.</p>
<h3>Libraries & tools</h3>
<p>A whole bunch of projects saw major releases recently.</p>
<ul>
<li>The static site generator: <a href="https://www.11ty.dev/blog/eleventy-v1-beta/">Eleventy 1.0 (beta)</a></li>
<li>The sometimes-static site generator: <a href="https://nextjs.org/blog/next-12">Next.js 12</a></li>
<li>The bundler: <a href="https://parceljs.org/blog/v2/">Parcel 2</a></li>
<li>The Markdown superset: <a href="https://v2.mdxjs.com/blog/v2/">MDX 2 (RC)</a></li>
<li>The popular client-side router: <a href="https://remix.run/blog/react-router-v6">React Router 6</a></li>
</ul>
<h2>Articles</h2>
<h3>Patterns and idioms</h3>
<p>In <a href="https://meiert.com/en/blog/html-common-idioms/">HTML Concepts: Common Idioms</a>, Jens Oliver Meiert points to a section in the HTML spec that proposes markup patterns for things that don't have dedicated elements, such as footnotes or breadcrumb navigation.</p>
<p><a href="https://daverupert.com/2021/10/native-html-tabs/">Let's Talk about Native HTML Tabs</a>, Dave Rupert on <a href="https://github.com/tabvengers/spicy-sections"><code><spicy-sections></code></a>: <q>With one HTML, we can solve two birds with one stone. We get an accordion and a tabs control from the same single element. We can actually use one element and have infinite affordances.</q></p>
<p>Speaking of native, in <a href="https://www.scottohara.me/blog/2019/03/05/open-dialog.html">Having an open dialog</a>, Scott O'Hara revisits the <code><dialog></code> element to see what has changed in the last couple of years: <q><code><dialog></code> is almost here. [...] But, until the <code><dialog></code> is actually fully delivered, I personally suggest continuing to use trusted and robust <a href="https://github.com/KittyGiraudel/a11y-dialog">custom dialogs</a>.</q></p>
<p><a href="https://css-irl.info/evaluating-clever-css-solutions/">Evaluating Clever CSS Solutions</a> by Michelle Barker. Designers have always exploited API affordances to their advantage. We're now seeing new idioms, enabled by recent additions to CSS, that are downright cryptic without documentation.</p>
<p><a href="https://web.dev/patterns/layout/">Layout Patterns</a>, a collection of layout patterns built using modern CSS. (Previously: <a href="https://smolcss.dev/">SmolCSS</a> by Stephanie Eckles, <a href="http://1linelayouts.glitch.me/">1-line layouts</a> by Una Kravets.)</p>
<h3>Security and performance</h3>
<p><a href="https://cloudfour.com/thinks/svg-icon-stress-test/">Which SVG technique performs best for way too many icons?</a> by Tyler Sticka explores the best way to include lots of SVGs on a page: <q>If you want all the features of SVG and your icons are well optimized, inline SVG is your best bet. Simple and performant. If your icons are complex or poorly optimized, or if you don’t need all the bells and whistles of SVG, image elements are the most performant option, especially using data URIs (encoded as escaped XML, not Base64).</q></p>
<p><a href="https://blog.mozilla.org/attack-and-defense/2021/11/03/finding-and-fixing-dom-based-xss-with-static-analysis/">Finding and Fixing DOM-based XSS with Static Analysis</a> from Mozilla's Frederik Braun discusses an ESLint plugin that spots unsafe coding practices, such as assigning to <code>innerHTML</code> without sanitizing, and plans for Firefox to support the native <a href="https://wicg.github.io/sanitizer-api/">Sanitizer API</a>.</p>
<h3>Making things</h3>
<p>Tom MacWright has been <a href="https://www.placemark.io/blog">blogging about technical decisions</a> in Placemark, his work-in-progress geospatial data editor.</p>
<p><a href="https://adactio.com/articles/18580">The State Of The Web</a> by Jeremy Keith. <q>There’s a great German word, ‘Verschlimmbessern’: the act of making something worse in the attempt to make it better. Perhaps we verschlimmbessert the web.</q></p>
<p><a href="https://css-tricks.com/the-greatest-css-tricks-vol-i-ebook-pdf-and-epub/">The Greatest CSS Tricks Vol. I eBook (PDF and EPUB)</a>. Chris Coyier enlisted the help of Baldur Bjarnason to produce an ebook of choice CSS Tricks articles.</p>
<p><a href="https://www.smashingmagazine.com/2021/11/smashing-podcast-episode-43/">Smashing Podcast #43: What Is Astro?</a>, a conversation between Drew McLellan and Matthew Phillips on the static site builder.</p>
<h3>Creative coding</h3>
<p><a href="https://ciechanow.ski/curves-and-surfaces/">Curves and Surfaces</a>, a massive interactive article by Bartosz Ciechanowski.</p>
<p>Assorted <a href="https://www.iquilezles.org/www/index.htm">useful maths</a> by Inigo Quilez, such as <a href="https://www.iquilezles.org/www/articles/bezierbbox/bezierbbox.htm">computing the bounding box of a Bézier curve</a>.</p>
<h2>Tools</h2>
<p><a href="https://cleanup.pictures/">CleanUp.pictures</a>, a web app and open-source project that lets you remove objects from images. <q>It uses LaMa, an open-source model from Samsung’s AI Lab to automatically & acurately redraw the areas that you delete.</q></p>
<p><a href="https://github.com/nextjournal/clerk">Clerk</a>, local-first notebooks for Clojure. <q>Clerk gives you a rich notebook experience, built on top of regular Clojure namespaces, enhanced with markdown comments.</q></p>
<p><a href="https://cooklang.org/">CookLang</a>, a lightweight recipe markup language.</p>
<h2>TIL</h2>
<p><strong>Showing GitHub issue titles inline.</strong> <a href="https://twitter.com/simonw/status/1449453462282928128">Simon Willison</a>: <q>if you paste in a link to an issue or PR in another repo it will display it as a truncated URL, but if you instead add it in a hyphenated bullet point it will display the title of the issue and and indicate if it is open or closed.</q> — very useful!</p>
<p><strong><code>Math.hypot()</code>.</strong> A function I write over and over is the distance between two points. I wasn't aware there's <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot"><code>Math.hypot()</code></a> to compute just that, so I can write it shorter <em>and</em> clearer:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// Before:</span><br /><span class="token keyword">let</span> distance <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">sqrt</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span>p2<span class="token punctuation">.</span>x <span class="token operator">-</span> p1<span class="token punctuation">.</span>x<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">+</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span>p2<span class="token punctuation">.</span>y <span class="token operator">-</span> p2<span class="token punctuation">.</span>y<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// After:</span><br /><span class="token keyword">let</span> distance <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">hypot</span><span class="token punctuation">(</span>p2<span class="token punctuation">.</span>x <span class="token operator">-</span> p1<span class="token punctuation">.</span>x<span class="token punctuation">,</span> p2<span class="token punctuation">.</span>y <span class="token operator">-</span> p1<span class="token punctuation">.</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>Fixed-angle gradients in SVG.</strong> Did you know the length of the CSS linear gradient depends on the object's width and height? I looked at how to obtain the SVG equivalent in <a href="https://observablehq.com/@danburzo/css-gradient-line">this Observable notebook</a>.</p>
<figure>
<video loop="" controls="" style="width: 100%; height: auto; max-width: 30em">
<source src="https://danburzo.ro/img/wsf/css-gradient-line.mp4" />
</video>
<figcaption>An animation of how the length of the CSS gradient line varies with the angle, making a butterfly shape.</figcaption>
</figure>
<h2>Media</h2>
<p><a href="https://learningmusic.ableton.com/">Learning Music</a>, interactive lessons by Ableton. <q>In these lessons, you'll learn the basics of music making. No prior experience or equipment is required; you'll do everything right here in your browser.</q> (Previously: <a href="https://learningsynths.ableton.com/">Learning Synths</a>)</p>
<p><strong>sicp.js</strong> <em>Structure and Interpretation of Computer Programs</em>, the classic textbook, gets <a href="https://mitpress.mit.edu/books/structure-and-interpretation-computer-programs-1">a JavaScript edition</a> next year. (via <a href="https://twitter.com/danrkports/status/1456141494952161281">Dan Ports</a>)</p>
wsf-xxx2021-10-19T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-10-19/<h2>Articles</h2>
<p><a href="https://www.smashingmagazine.com/2021/10/guide-debugging-css/">A Guide To CSS Debugging</a> by Stephanie Eckles. <q>We’ll look at a few categories bugs often fit into, see how we can evaluate the situation, and explore techniques that help prevent these bugs.</q></p>
<p>In <a href="https://lea.verou.me/2021/10/custom-properties-with-defaults/">Custom properties with defaults: 3+1 strategies</a> Lea Verou outlines ways to provide a styling API for your components using CSS custom properties.</p>
<p><a href="https://tetralogical.com/blog/2021/10/05/browsing-with-a-mobile-screen-reader/">Browsing with a mobile screen reader</a> by Henny Swan. A surprising bit: <q>people who use mobile screen readers are slightly more likely to use a mobile app than a web site to carry out tasks</q>. See <a href="https://webaim.org/projects/screenreadersurvey9/">The WebAIM 2021 Screen Reader User Survey</a> for more results.</p>
<p>Jonathan Neal has some test results on the various <a href="https://jonneal.dev/rat/hidden.html">accessibility-conscious ways to hide content</a>.</p>
<p>In <a href="https://web.dev/app-like-pwas/">Make your PWA feel more like an app</a>, Thomas Steiner goes through some features of a native app (with Apple Podcasts taken as an example), and shows how do implement the web platform equivalent.</p>
<p><a href="https://noti.st/aarongustafson/UoYFSo">Getting Started with PWAs</a>, a presentation in which Aaron Gustafson <q>will walk you, step-by-step, through the process of turning a website into a PWA.</q></p>
<p><a href="https://web.dev/sanitizer/">Safe DOM manipulation with the Sanitizer API</a> by Jack J. <q>The new Sanitizer API (currently in the prototyping phase in Chrome and Firefox) aims to build a robust processor for arbitrary strings to be safely inserted into a page.</q> Where the native solution is not available, <a href="https://github.com/cure53/DOMPurify">DOMPurify</a> can be used as a polyfill.</p>
<p>Huge if true: <a href="https://jakearchibald.com/2021/cors/">How to win at CORS</a>, a definitive guide by Jake Archibald. <q>I'm going to try to explain why CORS is the way it is, by looking at how it came into existence, and how it fits into other kinds of fetches. Wish me luck…</q></p>
<p><a href="https://jaredgorski.org/writing/15-practical-frontend-philosophy/">Practical frontend philosophy</a> by Jared Gorski. <q>Since frontend supports the interface between computers and humans, a well-constructed frontend is a vital part of the greater hardware-software-human relationship.</q></p>
<p><a href="https://www.baldurbjarnason.com/2021/five-ways-to-get-out-of-the-event-handling-mess/">The event listening toolkit: five ways to get out of an event handling mess</a>, Baldur Bjarnasson on an essential area of front-end engineering.</p>
<p><a href="https://web.dev/building-a-multi-select-component/">Building a multi-select component</a>, <q>a foundational overview of how to build a responsive, adaptive, and accessible, multiselect component for sort and filter user experiences</q> by Adam Argyle</p>
<p><a href="https://www.w3.org/TR/design-principles/">Web Platform Design Principles</a>, <q>a set of design principles to be used when designing Web Platform technologies</q>, useful for designing other APIs as well.</p>
<p><a href="https://philipwalton.com/articles/my-challenge-to-the-web-performance-community/">My Challenge to the Web Performance Community</a> by Philip Walton</p>
<p><a href="https://daverupert.com/2021/10/html-with-superpowers/">HTML with Superpowers</a>, a talk by Dave Rupert on Web Components. <q>I think if you were using Web Components before 2020 you were an early adopter and you probably have some scars to show for it. But in 2021, now that all modern browsers support Web Components, I think they’re worth investigating. They have one superpower that no other JavaScript framework offers called the Shadow DOM which is both powerful but frustrating. But another superpower — the power I’m most excited about — is that you can use them standalone without any frameworks, build tools, or package managers.</q></p>
<p>A couple of videos from the Jamstack 2021 conference: <a href="https://www.youtube.com/watch?v=860d8usGC0o">Have Single-Page Apps Ruined the Web?</a> by Rich Harris, and <a href="https://www.youtube.com/watch?v=fop0HoArXxE">Astro Lightning Launch: Astro v0.21</a> by Matthew Phillips.</p>
<h2>Tools</h2>
<p><a href="https://github.com/speedyg0nz/MagInkCal">MagInkCal</a>, an <q>E-Ink Magic Calendar that automatically syncs to Google Calendar and runs off a battery powered Raspberry Pi Zero</q>.</p>
<p><a href="https://sketchmachine.net/">Sketch Machine</a> by Casey Reas is a supremely fun animation tool.</p>
<h2>TIL</h2>
<h3>You can copy multiple spans of text in Firefox</h3>
<p>I do this all the time in Sublime Text: hold down the <kbd>⌘</kbd> key and double-click on words to create a multiple selection. The other day I casually did that, without noticing, in Firefox and… it worked?</p>
<p>I think I was aware of this feature before because I vaguely remember being puzzled by <a href="https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt"><code>Selection.getRangeAt</code></a> asking for a range <em>index</em> — <q>wait, there's more than one?!</q> — but the moment felt like discovering it anew. The <a href="https://w3c.github.io/selection-api/">Selection API spec</a> (currently a draft) has some interesting historical tidbits:</p>
<blockquote>
<p>Originally, the Selection interface was a Netscape feature. The original implementation was carried on into Gecko (Firefox), and the feature was later implemented independently by other browser engines. The Netscape implementation always allowed multiple ranges in a single selection, for instance so the user could select a column of a table. However, multi-range selections proved to be an unpleasant corner case that web developers didn't know about and even Gecko developers rarely handled correctly. Other browser engines never implemented the feature, and clamped selections to a single range in various incompatible fashions.</p>
</blockquote>
<h3>Switching to ESM packages</h3>
<p>In the last few months, I've begun noticing more and more major version bumps as I worked my way through <a href="https://danburzo.ro/releasing-js/updating-deps">updating dependencies</a> for various projects, and a pattern emerged:</p>
<p>Packages are starting to switch to using ES modules natively. The move has been accelerated by Node.js 10, the last version that doesn't support modules out of the box, being taken out of <a href="https://nodejs.org/en/about/releases/">long-term support</a> earlier this year. Since you can't (conveniently) use ESM packages unless your project is also switched over, Rich Harris has <a href="https://twitter.com/Rich_Harris/status/1441068317930819589">astutely pointed out</a> widespread transition will happen sooner rather than later.</p>
<p>I've taken the plunge with new major versions of some of my projects (<a href="https://github.com/danburzo/percollate/releases/tag/v2.0.0">percollate</a>, <a href="https://github.com/Evercoder/culori/releases/tag/v1.0.0">culori</a>) that are now ESM-first. In the process, I've started writing down some pointers on <a href="https://danburzo.ro/releasing-js/switching-to-esm">switching to native ESM</a>, as part of a new <a href="https://danburzo.ro/releasing-js/">Releasing JavaScript</a> section on this website.</p>
<h2>WSF-approved media</h2>
<h3>Books</h3>
<p><a href="http://manualofdiacritics.eu/">Manual of diacritics</a>: <q>The 1990s, a time of rapid growth in digital typography, did not bring the same growth in interest in conceptual language support for typefaces, i.e., the creation of characters with diacritics. This gave rise to a situation well known to all graphic designers: A typeface they wanted to use in their design does not contain all the characters in their language.</q> (via <a href="https://www.robinrendle.com/notes/manual-of-diacritics/">Robin Rendle</a>)</p>
<p><strong>On writing.</strong> <a href="https://agilecommshandbook.com/">The agile comms handbook</a> by <a href="https://gilest.org/">Giles Turnbull</a>, <q>helps you use a human tone of voice, and a little creative flair, to get your point across to busy people</q>. Also out: <a href="https://docsfordevelopers.com/">Docs for Developers: An Engineer’s Field Guide to Technical Writing</a> on Apress.</p>
<h3>Ambient ensemble</h3>
<p><a href="https://coldcut.bandcamp.com/album/0">@0</a>, a compilation of new ambient music whose lineup sounds, frankly, like the perfect music festival, will be released in November. Ninja Tune's proceeds will go to three mental health charities: CALM (Campaign Against Living Miserably), Mind and Black Minds Matter.</p>
<h3>Documentaries</h3>
<p><a href="https://films.nationalgeographic.com/becoming-cousteau">Becoming Cousteau</a>, directed by Liz Garbus. <a href="https://www.focusfeatures.com/roadrunner/">Roadrunner</a>, a film about Anthony Bourdain directed by Morgan Neville.</p>
wsf-xxix2021-10-04T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-10-04/<h2>News</h2>
<p>Safari was updated to version 15 across iOS, iPadOS and macOS. <a href="https://danburzo.ro/on-safari-15">I took a look at some of the features</a> in the final release, including the new color syntaxes, <code>theme-color</code>, the redesigned color picker, and more.</p>
<p><a href="https://developer.chrome.com/blog/new-in-chrome-94/">Chrome 94 was released</a>, with support for the <code>display-p3</code> color space in the HTML <code><canvas></code> element.</p>
<h2>Articles</h2>
<h3>CSS</h3>
<p>In <a href="https://www.smashingmagazine.com/2021/09/reducing-need-pseudo-elements/">Reducing The Need For Pseudo-Elements</a> Marcel Moreau shows how <code>clip-path</code>, <code>linear-gradient()</code> and <code>background-blend-mode</code> can replace traditional techniques based on pseudo-elements.</p>
<p><a href="https://www.bram.us/2021/09/15/the-future-of-css-cascade-layers-css-at-layer/">The Future of CSS: Cascade Layers (CSS <code>@layer</code>)</a>, a detailed guide by Bramus to declaring separate layers for CSS rules using <code>@layer</code>. It's a new feature in the <a href="https://www.w3.org/TR/css-cascade-5/#layering">CSS Cascade and Inheritance Level 5</a> specification, so that <q>authors can create layers to represent element defaults, third-party libraries, themes, components, overrides, and other styling concerns—and are able to re-order the cascade of layers in an explicit way, without altering selectors or specificity within each layer, or relying on source-order to resolve conflicts across layers</q>.</p>
<h3>JavaScript</h3>
<p><a href="https://www.baldurbjarnason.com/2021/fetch-and-formdata/">FormData and fetch, why is serialising a form such a pain?</a>, asks Baldur Bjarnason. Also on the subject: all the ways of <a href="https://jakearchibald.com/2021/encoding-data-for-post-requests/">encoding data for POST requests</a>, by Jake Archibald.</p>
<p><a href="https://dev.to/thepassle/web-components-from-zero-to-hero-4n4m">Web Components: from zero to hero</a>, a three-part series by Pascal Schilp.</p>
<h3>Dev tools</h3>
<p><a href="https://blog.typicode.com/husky-git-hooks-javascript-config/">Why husky has dropped conventional JS config</a>. Husky is a tool that helps you, and anyone <code>npm install</code>-ing your repo, set up Git hooks to trigger scripts when you perform a Git action, such as commit<code>commit</code>). The design of hooks is not particularly conductive to this sort of workflow, but Git's ability to configure the path to hooks (with the <code>core.hooksPath</code> option) makes it a bit more straightforward.</p>
<p><a href="https://css-tricks.com/comparing-the-new-generation-of-build-tools/">Comparing the New Generation of Build Tools</a>, a summary by Hugh Haworth covering esbuild, Snowpack, Vite, and wmr.</p>
<h3>Interaction Design, Accessibility</h3>
<p><a href="https://tetralogical.com/blog/2021/09/29/browsing-with-a-desktop-screen-reader/">Browsing with a desktop screen reader</a>, a very useful overview by Henny Swan. (Update: there's a companion piece about <a href="https://tetralogical.com/blog/2021/10/05/browsing-with-a-mobile-screen-reader/">mobile screen readers</a>.) Related: <a href="https://www.tpgi.com/screen-readers-accessibility-testing/">How can screen readers be used in accessibility testing?</a> by Marissa Sapega.</p>
<p><a href="https://www.nngroup.com/articles/empty-state-interface-design/">Designing Empty States in Complex Applications: 3 Guidelines</a> by Kate Kaplan. <q>Empty states provide opportunities for designers to communicate system status, increase learnability of the system, and deliver direct pathways for key tasks. This article provides guidance for designing empty-state dialogues for content-less containers.</q></p>
<p><a href="https://www.scottohara.me//blog/2021/09/24/custom-radio-checkbox-again.html">One last time: custom styling radio buttons and checkboxes</a> by Scott O'Hara. <q>Now (2021), with Internet Explorer support being dropped left and right, and Edge now being Chromium-based, and Firefox quirks having been ironed out, these limitations have largely lifted. I hope this is the last time I write about this subject.</q></p>
<p><a href="https://accessibility.blog.gov.uk/2021/09/21/an-update-on-the-accessibility-of-conditionally-revealed-questions/">An update on the accessibility of conditionally revealed questions</a> by the GOV.UK Acccessibility team.</p>
<p><a href="https://textslashplain.com/2017/01/14/the-line-of-death/">The Line of Death</a>, i.e. the line between trusted (browser) and untrusted (web page) UI. Interesting perspective! (via <a href="https://twitter.com/hsivonen/status/1444644657800089602">Henri Sivonen</a>)</p>
<h3>Creative coding</h3>
<p><a href="https://rd.nytimes.com/projects/an-end-to-end-guide-to-photogrammetry-with-mobile-devices">An End-to-End Guide to Photogrammetry with Mobile Devices</a>, an impressive series of guides by the New York Times R&D Lab. <q>Sophisticated 3D experiences can be expensive and complex productions, often requiring the use of specialized equipment such as mirrorless cameras and drones, as well as a fair amount of custom coding. Fortunately, the latest generation of smartphones contain powerful cameras, processors and apps that make it increasingly possible for anyone with a little technical proficiency to create publication-quality 3D models.</q></p>
<p><a href="https://thedigitalreview.com/index.html">the digital review</a>, <q>an annual journal dedicated to the preservation and publication of innovative, born-digital essays. Each theme-based issue will offer a curated combination of commissioned work, submitted work and "rediscovered" work.</q></p>
<p><a href="https://revdancatt.com/2020/08/26/how-to-pen-plot">Tiny advice for "I want to make pen plotter art, help!"</a>, lovely pointers by Rev Dan Catt.</p>
<p><a href="https://hicks.design/journal/moo-card-player">Moo Card Player</a>, Jon Hicks on using NFC tags to automatically play music on the iPhone.</p>
<h3>The Unix CLI</h3>
<p><strong>Search & replace with <code>ripgrep</code>.</strong> The tool is primarily meant for matching and extracting patterns, but with a bit of effort you can replace text in the original files. A couple of guides: <a href="https://hackaday.com/2020/10/14/linux-fu-global-search-and-replace-with-ripgrep/">Linux Fu: Global Search And Replace With Ripgrep</a> by Al Williams, and <a href="https://learnbyexample.github.io/substitution-with-ripgrep/">Search and replace tricks with ripgrep</a> by Sundeep Agarwal.</p>
<p>See also: <a href="https://earthly.dev/blog/awk-examples/">Understading <code>awk</code></a> by Adam Gordon Bell.</p>
<h2>Tools & resources</h2>
<h3>Online tools</h3>
<p><a href="https://jvns.ca/blog/2021/09/24/new-tool--an-nginx-playground/">An nginx playground</a> by Julia Evans. <q>I was talking to a friend about how it would be cool to have an nginx playground website where you can just paste in an nginx config and test it out. And then I realized it might actually be pretty easy to build, so got excited and started coding and I built it.</q></p>
<p><a href="https://sqlime.org/">SQLime</a>, <q>an online SQLite playground for debugging and sharing SQL snippets. Kinda like JSFiddle, but for SQL instead of JavaScript.</q></p>
<p><a href="https://sia.codes/posts/lighthouse-treemap/">Explore JavaScript Dependencies With Lighthouse Treemap</a>, <q>discover all JavaScript downloaded and used/unused for a site in a handy data visualization</q>.</p>
<p>Yes please! <a href="https://search.marginalia.nu/">Marginalia Search</a> is a search that favors text-heavy, old-school websites.</p>
<h3>Libraries</h3>
<p><a href="https://mechanic.design/">Mechanic</a>, <q>an open source framework that makes it easy to create custom, web-based design tools that export design assets right in your browser.</q></p>
<p><a href="https://www.theatrejs.com/">Theatre.js</a> by Aria Minaei, <q>a JavaScript animation library with a GUI. It animates the DOM, WebGL, and any other JavaScript variable.</q></p>
<p><a href="https://meodai.github.io/fettepalette/">FettePalette</a> by David Aerne, a <q>color ramp generator using curves within the HSV color model</q>.</p>
<p><a href="https://github.com/marceloprates/prettymaps">prettymaps</a> by Marcelo Prates, <q>a small set of Python functions to draw pretty maps from OpenStreetMap data</q>.</p>
<p><a href="https://github.com/BuilderIO/partytown">partytown</a>, <q>relocate resource intensive third-party scripts off of the main thread and into a web worker</q>. Super smart.</p>
<p><a href="https://csswizardry.com/ct/">ct.css</a>, <q>a diagnostic CSS snippet that exposes potential performance issues in your page’s <code><head></code> tag.</q></p>
<h3>Resources</h3>
<p><a href="https://patterns.helloyes.dev/">Basic Pattern Repository</a>, copy-pastable SVG patterns by Thomas Michael Semmler.</p>
<p><a href="https://frontenddogma.com/">Frontend Dogma</a>, <q>articles and books, tips and tricks, craft and beauty from the world of frontend development</q>, by Jens Oliver Meiert.</p>
<h2>TIL</h2>
<h3>Cropping a video on macOS</h3>
<p>I had made a screen recording on iOS and was looking for a way to focus in on a single detail in the video. The Preview app on macOS doesn't have a way to crop a video the same way you would a static image, but I found a devious idea online: simply record an area of of your screen while the video is playing in Quicktime. The keyboard shortcut is <kbd>Command + Shift + 5</kbd>.</p>
<h3>Mutating array methods</h3>
<p>JavaScript's array API is… let's say, eclectic. Some methods change the original array, <a href="https://doesitmutate.xyz/">some don't</a>. The distinction may be inconsequential but then, infrequently and catastrophically, a missing <code>slice()</code> in front of <code>reverse()</code> really does a number on you. I was thinking on ways to prevent that from happening, once and for all, and wondered if there's any initiative to fix this. Turns out there is! <a href="https://github.com/tc39/proposal-change-array-by-copy">tc39/proposal-change-array-by-copy</a>, which <q>provides additional methods to <code>Array.prototype</code> to enable changes on an array by returning a new copy of it with the change</q>, is in Stage 2. Not convinced about the names (<code>withSorted()</code>, <code>withReversed()</code>, etc.), but <a href="https://github.com/tc39/proposal-change-array-by-copy/issues/10">those might change</a>.</p>
<hr />
<p><strong>A wholesome browser game.</strong> <a href="https://lingyourlanguage.com/">LingYourLanguage</a>, <q>a collaborative effort to bring the world’s languages to a wider audience in an entertaining, engaging way.</q> The goal is to guess the language being spoken in short audio clips. (Via <a href="https://twitter.com/Recomendo6">Recomendo</a>)</p>
<p><strong>A book.</strong> Jeremy Wagner's <a href="https://responsiblejs.dev/">Responsible JavaScript</a> is available for pre-order.</p>
<p><strong>Two albums.</strong> José González's <a href="https://josgonzlez.bandcamp.com/album/local-valley">Local Valley</a> and Saint Etienne's <a href="https://heavenlyrecordings.bandcamp.com/album/ive-been-trying-to-tell-you">I've Been Trying To Tell You</a> have been on heavy rotation these few weeks.</p>
<p><strong>Two documentaries.</strong> <a href="https://www.altitude.film/page/oliver-sacks-his-own-life">Oliver Sacks: His Own Life</a>, a film by Ric Burns. <a href="https://www.lastandfirstmen.com/trailer">Last and first men</a>, a film by Jóhann Jóhannsson narrated by Tilda Swinton.</p>
wsf-xxviii2021-09-14T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-09-14/<h2>News</h2>
<h3>Browsers & the web platform</h3>
<p>Firefox 92 is out. It joins Chrome and Edge in supporting the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/accent-color"><code>accent-color</code> CSS property</a> for form inputs. <q>Trying to stop developers from styling checkboxes and radio buttons is like trying to stop teenagers from having sex. You might as well accept that it’s going to happen and give them contraception so they can at least do it safely.</q> <a href="https://adactio.com/journal/18442">says Jeremy Keith</a>. Ruth John <a href="https://hacks.mozilla.org/2021/09/time-for-a-review-of-firefox-92/">summarizes other new features</a>.</p>
<p>Browser dev tools move pretty fast these days. Patrick Brosset offers an <a href="https://www.smashingmagazine.com/2021/09/devtools-cross-browser-edition/">overview of recent additions across browsers</a>.</p>
<p><a href="https://w3c.studio24.net/updates/design-system-live/">The W3C Design System is live</a> and is looking mighty fine. Andy Bell's <a href="https://cube.fyi/">Cube CSS</a> gets a shoutout as an inspiration.</p>
<p>New explainer just dropped: <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">HTML <code>focusgroup</code> attribute</a> aims to help authors implement keyboard navigation for custom controls. Like with other explainers, feedback via GitHub issues is encouraged.</p>
<h3>Security updates</h3>
<p><a href="https://github.blog/2021-09-08-github-security-update-vulnerabilities-tar-npmcli-arborist/">Upgrade <code>npm</code> to 6.14.15 or 7.21.0, respectively</a> to prevent malicious packages from potentially overwriting files outside <code>node_modules</code>.</p>
<p><a href="https://wordpress.org/news/2021/09/wordpress-5-8-1-security-and-maintenance-release/">Upgrade to WordPress 5.8.1</a> to patch some vulns in the REST API and Gutenberg editor. Probably upgrade your plugins for good measure while you're there.</p>
<h2>Things to read</h2>
<h3>Fresh <span class="ae">aesthetics</span> with CSS & SVG</h3>
<p><a href="https://www.joshwcomeau.com/css/designing-shadows/">Designing Beautiful Shadows in CSS</a>, Josh W. Comeau on overlapping <code>box-shadow</code>s for a realistic elevation effect. The <code>drop-shadow()</code> filter also gets a quick mention.</p>
<p><a href="https://css-tricks.com/grainy-gradients/">Grainy Gradients</a> by Jimmy Chion, and a companion <a href="https://grainy-gradients.vercel.app/">interactive playground</a>.</p>
<h3>Accessibility</h3>
<p><em>ARIA Spec for the Uninitiated</em>, a series by Gerard Cohen on the Deque blog: <a href="https://www.deque.com/blog/aria-spec-for-the-uninitiated-part-1/">Part 1</a>, <a href="https://www.deque.com/blog/aria-spec-for-the-uninitiated-part-2/">Part 2</a>, and <a href="https://www.deque.com/blog/aria-spec-for-the-uninitiated-part-3/">Part 3</a>.</p>
<p><a href="https://www.uxmatters.com/mt/archives/2021/09/color-and-universal-design.php">Color and Universal Design</a> by Steven Hoober.</p>
<h3>JavaScript</h3>
<p><a href="https://web.dev/eventing-deepdive/">JavaScript Eventing Deep Dive</a>, Stephen Stchur
and Thomas Steiner on the nitty-gritty of DOM Events. (One of the mini-projects chronically stuck in my drafts folder is documenting what exactly you prevent when you <code>preventDefault()</code> but, as the authors note, there's a daunting number of combinations to test.)</p>
<p><a href="https://web.dev/building-a-split-button-component/">Building a split-button component</a>, another foundational overview by Adam Argyle, following his write-up on <a href="https://web.dev/building-a-switch-component/">building switches</a>.</p>
<h3>Performance</h3>
<p><a href="https://nolanlawson.com/2021/09/12/how-to-write-about-web-performance/">How to write about web performance</a>, in which Nolan Lawson tries to <q>distill some of what I’ve learned over the years to offer as advice to other aspiring tinkerers, benchmarkers, and anyone curious about how browsers actually work when you put them to the test.</q></p>
<h3>Build tools, and doing without</h3>
<p><a href="https://giuseppegurgone.com/twitter-html/">Twitter's div Soup and Uglyfied CSS, Explained</a> by Giuseppe Gurgone. <q>To the eyes of somebody who’s not familiar with the framework, the HTML produced by React Native for Web might look utterly ugly and full of bad practices. In this blog post we will see how to make sense of this source code and why the authors made some unconventional and controversial choices.</q></p>
<p><a href="https://web.dev/bundling-non-js-resources/">Bundling non-JavaScript resources</a> by Ingvar Stepanyan about the <code>new URL('./relative/path', import.meta.url)</code> pattern for loading assets in a way that works for browsers as well as bundlers.</p>
<p><a href="https://elisehe.in/2021/08/22/using-the-platform">Using the platform</a> by Elise Hein. <q>Working on a codebase with no dependencies has been a way of rediscovering exactly what I get for free in 2021.</q></p>
<h3>Designing & building software</h3>
<p>An accessible <a href="https://xstate.js.org/docs/guides/introduction-to-state-machines-and-statecharts/">Introduction to state machines and statecharts</a> by Laura Kalbag, featuring a puppy doing puppy things.</p>
<p><a href="https://articles.uie.com/users-dont-hate-change-they-hate-our-design-choices/">Users Don’t Hate Change. They Hate Our Design Choices.</a> (2018) by Jared Spool. Useful for the next time this meme rears its head in conversation.</p>
<p><a href="https://www.baldurbjarnason.com/2021/single-page-app-morality-play/">The Single-Page-App Morality Play</a>. In the debate between SPA vs. MPAs, an aspect often overlooked is that <q>the problem is management</q>, says Baldur Bjarnason.</p>
<h3>Reading, Writing, Publishing</h3>
<p><a href="https://interconnected.org/home/2021/09/09/rewriting">The surprising effectiveness of writing and rewriting</a>, Matt Webb on why getting that first draft out of the way works so well.</p>
<p><a href="https://daniel.haxx.se/blog/2021/09/04/making-world-class-docs-takes-effort/">Making world-class docs takes effort</a>, says <code>curl</code> creator Daniel Stenberg.</p>
<p><a href="https://smus.com/file-systems-for-thought/">File Systems for Thought</a> by Boris Smus. <q>I'm excited to see a rennaisance in the Tools for Thought space. Hopefully new entrants embrace a more interoperable approach, while still building sustainable businesses. But for now, I have kludged together various pieces that together feel more like a coherent System for Thought, address my specific needs, and allow for interesting experiments in the future.</q></p>
<p><a href="https://jarango.com/2021/09/07/how-new-note-taking-apps-give-you-information-management-superpowers/">How New Note-taking Apps Give You Information Management Superpowers</a> by Jorge Arango.</p>
<h2>Tools</h2>
<h3>Color</h3>
<p><a href="https://bottosson.github.io/posts/colorpicker/">Okhsv and Okhsl, Two new color spaces for color picking</a> by Björn Ottosson, who has previously released the <a href="https://bottosson.github.io/posts/oklab/">Oklab</a> color space.</p>
<h3>Codemods & semantic search</h3>
<p>I <a href="https://danburzo.ro/javascript-codemods/">wrote an article</a> on using <code>jscodeshift</code> to search for patterns through JavaScript code. Maybe it has influenced my awareness, or the topic happens to be in the zeitgeist, but a couple of related things popped up on my timeline:</p>
<ul>
<li><a href="https://bytes.zone/posts/tree-grepper/">tree-grepper</a> by Brian Hicks;</li>
<li><a href="https://github.com/sachinchoolur/replace-jquery">replace-query</a> by Sachin Neravath, which finds jQuery methods and generates vanilla JS alternatives.</li>
</ul>
<h3>Command-line tools for HTML</h3>
<p>Two new HTML data extraction tools on my radar: <a href="https://github.com/mgdm/htmlq">htmlq</a> is <q>like <code>jq</code>, but for HTML</q>, using CSS selectors to extract data from pages. <a href="https://github.com/benibela/xidel">xidel</a> lets you use <q>CSS selectors, XPath 3.0, XQuery 3.0, JSONiq or pattern matching</q>. I've just added them to the list of related tools I collect in the repository for <a href="https://github.com/danburzo/hred#related-projects">hred</a>, which is my on take on this space.</p>
<p><a href="https://github.com/Cykelero/timefind">timefind</a> is a command-line tool by Nathan Manceaux-Panot to find the exact moment that something was added to a website, using the Internet Archive's Wayback Machine.</p>
<h3>Smaller & faster</h3>
<p><a href="https://statoscope.tech/">Statoscope</a> is a detailed Webpack bundle analyzer.</p>
<p><a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a>, Jake Archibald's online front-end to <a href="https://github.com/svg/svgo">SVGO</a>, an essential tool for optimizing SVG files, received its first update in a while. For raster images, <a href="https://squoosh.app/">Squoosh</a> is similarly excellent.</p>
<h3>Static sitegen / CMS</h3>
<p>I haven't paid close attention so it was news to me, but in December 2020 <a href="https://getkirby.com/releases/3.5">Kirby 3.5</a> introduced a block editor. This lightweight CMS may have become just powerful enough to compete with WordPress, and I look forward to trying it on a new project.</p>
<p><a href="https://www.tiptap.dev/">Tiptap</a>, <q>the headless editor framework for web artisans</q>, based on ProseMirror.</p>
<h3>Wholesome experiments</h3>
<p>These physical <a href="https://optional.is/required/2021/09/06/golden-ratio-rulers/">Golden Ratio Rulers</a> by Brian Suda are simpatico. But then the rulers are used on a tablet! With a digital pen! And it works! (Of course it does, why wouldn't it, but I kind of haven't considered it. I love tiny epiphanies.)</p>
<p>I've tried, at various points in time, to picture the daylight-by-latitude chart in my head and now Dan Bridges has made a lovely Observable notebook for it: <a href="https://observablehq.com/@dbridges/visualizing-seasonal-daylight">Visualizing Seasonal Daylight</a>.</p>
<h2>Resources</h2>
<p><a href="https://designsystemsrepo.com/">Design Systems Repo</a>, a <q>frequently updated collection of Design System examples, articles, tools and talks.</q></p>
<p><a href="https://a11y-automation.dev/">A11yAutomation</a>: <q>It can be hard to keep track of the potential accessibility violations in your code. More automation is being created for linting rules and tests — and now there is a single site to track the automation available to developers.</q></p>
<p><a href="https://codecatalog.org/">Code Catalog</a>, a collection of code examples from prominent open-source projects, maintained by Anton Emelyanov. Reminds me of the <a href="http://aosabook.org/en/index.html">Architecture of Open Source Applications</a> book series.</p>
<p>I can't remember if I was aware of Thoughtwork's <a href="https://www.thoughtworks.com/radar">Technology Radar</a> before (or whether I'm overlapping <a href="https://www.oreilly.com/radar/">O'Reilly Radar</a> onto it), but I enjoy the format. Looking around for familiar tech, I saw sensible commentary, but it's worth taking the advice with a grain of salt: yes, I'm looking at you, <a href="https://www.thoughtworks.com/radar/techniques/micro-frontends">micro frontends</a>.</p>
<hr />
<p>Sources for this edition: <a href="https://csslayout.news/">CSS Layout News</a>, <a href="https://joy.recurse.com/">Joy of Computing</a>, <a href="https://css-weekly.com/">CSS Weekly</a>, <a href="https://notes.baldurbjarnason.com/">Baldur Bjarnason</a>, <a href="https://twitter.com/m_ott">Matthias Ott</a>, <a href="https://twitter.com/stevefaulkner">Steve Faulkner</a>, <a href="https://twitter.com/fvsch">Florens Verschelde</a>, <a href="https://twitter.com/simevidas">Šime Vidas</a>.</p>
wsf-xxvii2021-09-03T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-09-03/<h2>News</h2>
<p><a href="https://www.w3.org/TR/css-nesting-1/">CSS Nesting Module</a>, which introduces the ability to nest one style rule inside another, has been published as a Working Draft. The syntax is a good fit for <a href="https://github.com/danburzo/hred">hred</a> to make nested queries shorter as a replacement for <code>:scope</code>.</p>
<p>Pre-recorded talks & slides are up for the <a href="https://www.w3.org/Graphics/Color/Workshop/talks.html">W3C Workshop on Wide Color Gamut and HDR for the Web</a>, and a live Questions & Answers session is due for September 13.</p>
<p>Relatedly, the <a href="https://github.com/WICG/eyedropper-api">Eyedropper API</a> is available in Chrome 95 (Canary). It uses a Promise to communicate its outcome:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> eyedropper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EyeDropper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />button<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> eyedropper<br /> <span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">result</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>sRGBHex<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'No selection'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>...although some choices are <a href="https://github.com/w3ctag/design-reviews/issues/587#issuecomment-911546557">still being debated</a>. For now, the result only includes the hex code for the color (under the hyper-specific <code>sRGBHex</code> property), but it's expected to make use of the <a href="https://github.com/WICG/color-api">Color API</a> when that's ready.</p>
<p><a href="https://github.blog/2021-08-24-github-cli-2-0-includes-extensions/">GitHub CLI 2.0 was released</a>, with added support for extensions, opens up a lot of fun possibilities.</p>
<h2>Things to read & watch</h2>
<p><a href="https://www.getrevue.co/profile/shift-happens/issues/moire-no-more-688319">Moiré no more</a> by Marcin Wichary, on using a FFT (<a href="https://en.wikipedia.org/wiki/Fast_Fourier_transform">Fast Fourier transform</a>) to remove patterns from images. Incredible stuff.</p>
<p><a href="https://blog.mozilla.org/en/internet-culture/deep-dives/why-are-hyperlinks-blue/">Why are hyperlinks blue?</a> by Elise Blanchard. I love a good trip down memory la— eh, who am I kidding, I got my first computer in the early 2000s (team <a href="https://en.wikipedia.org/wiki/Windows_Me">Windows Me</a>). Interesting nonetheless.</p>
<p><a href="https://blog.placemark.io/2021/08/27/tech-brief-json-pointer.html">Tech brief: JSON Pointer</a> by Tom MacWright about the new-to-me <a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> specification.</p>
<p><a href="https://www.baldurbjarnason.com/2021/software-crisis-2/">Software Crisis 2.0</a> by Baldur Bjarnason, <q>An essay on our industry's core expertise: failed software projects</q>.</p>
<p><a href="http://acko.net/blog/on-variance-and-extensibility/">On Variance and Extensibility</a> by Steven Wittens. <q>Making code reusable is not an art, it's a job</q>.</p>
<p><a href="https://ericwbailey.design/writing/what-they-dont-tell-you-when-you-translate-your-app/">What they don’t tell you when you translate your app</a> by Eric Bailey. <q>Forget inverting binary trees, translating or localizing a digital experience is one of the most difficult things you can do with software.</q></p>
<h3>Fixing and/or making browsers</h3>
<p><a href="https://blogs.windows.com/msedgedev/2021/08/10/compat2021-css-grid-gridng/">Improving CSS Grid compatibility with GridNG</a>, a rewrite of the CSS Grid module in Chromium-based browsers that fixes some long-standing bugs.</p>
<p>Still on the topic of <strong>N</strong>ew<strong>G</strong>eneration things, <a href="https://developer.chrome.com/blog/renderingng-data-structures/">Key data structures and their roles in RenderingNG</a>.</p>
<p>Finally, <a href="https://browser.engineering/">Web Browser Engineering</a> is a work-in-progress book by Pavel Panchekha & Chris Harrelson. <q>Web browsers are ubiquitous, but how do they work? This book explains, building a basic but complete web browser, from networking to JavaScript, in a thousand lines of Python.</q> Don't miss <a href="https://browser.engineering/bibliography.html">the bibliography</a> for more reads.</p>
<h3>Filtered for <code>@keyframes</code></h3>
<p>Josh W. Comeau has published <a href="https://www.joshwcomeau.com/animation/keyframe-animations/">An Interactive Guide to CSS Keyframe Animations</a>.</p>
<p>An ingenious use for animations is for interpolating font sizes (or other CSS properties, for that matter). It's <a href="https://docs.typetura.com/how-typetura-works">the insight that powers Typetura</a>, for example: use the <code>animation-delay</code> property as a knob to scrub through a paused animation.</p>
<p>It would be great to have this technique work in CSS alone, but currently the <code>animation-delay</code> property <a href="https://github.com/w3c/csswg-drafts/issues/558">only accepts a <code><time></code></a> and CSS lacks a function to strip units off lenghts, and so it seems impossible to <code>calc()</code> a delay based on the viewport's dimensions due to incompatible units.</p>
<p>(What we do have is <em>other</em> CSS functions that will make <a href="https://css-tricks.com/simplified-fluid-typography/">fluid typography easier to think about</a> once they're uniformly supported.)</p>
<h2>New books</h2>
<p>On the desk: <a href="https://mitpress.mit.edu/books/biography-pixel">A Biography of the Pixel</a> by Alvy Ray Smith (Pixar co-founder). I started in the middle with the section on splines, but the whole book seems super interesting. Also promising: <a href="https://nostarch.com/kill-it-fire">Kill it with fire</a>, on dealing with legacy code, by Marianne Bellotti.</p>
<p><em>Everything I Know about Life I Learned from Powerpoint</em> by Russell Davies <a href="https://russelldavies.typepad.com/planning/2021/08/let-the-marketing-begin.html">is available for pre-order</a>.</p>
<h2>A few tweaks</h2>
<p>I've made a few tweaks to the format of these w/s/f posts to make them less appalling to screen reader users. Out with the slashes, in with the paragraphs & headings.</p>
<p>Instead of the ° (degrees) character, which gets read out loud by assistive technology, external links are now adorned with a contentless circle. (Excuse the pixel units, but <code>em</code>s still don't get rendered reliably.)</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.post a[href^='http']::after</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> 7px<span class="token punctuation">;</span><br /> <span class="token property">height</span><span class="token punctuation">:</span> 7px<span class="token punctuation">;</span><br /> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span><br /> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> 0.1em solid<span class="token punctuation">;</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span><br /> <span class="token property">vertical-align</span><span class="token punctuation">:</span> super<span class="token punctuation">;</span><br /> <span class="token property">margin-inline</span><span class="token punctuation">:</span> 0.15em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>A little weekend project I'd toy with is to try & see what kinds of simple shapes you can produce with just one CSS pseudo-element (surely this has been done a thousand times before). Multiple overlapping gradients feel like cheating but <a href="https://css-tricks.com/drawing-images-with-css-gradients/">take you pretty far</a>.</p>
wsf-xxvi2021-08-22T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-08-22/<p><strong>Creative coding.</strong> <a href="https://acegikmo.com/mathvis/index.html">Math visualizations</a> by Freya Holmér; <a href="https://www.youtube.com/watch?v=aVwxzDHniEw">The Beauty of Bézier Curves</a> is spectacular</p>
<p><strong>UI / Accessibility.</strong> <a href="https://www.sarasoueidan.com/blog/focus-indicators/">A guide to designing accessible, WCAG-compliant focus indicators</a>, a deep-dive by Sara Soueidan / <a href="https://web.dev/building-a-switch-component/">Building a switch component</a>, an overview of how to build a responsive and accessible component by Adam Argyle /
<a href="https://kittygiraudel.com/2021/08/20/accessibility-from-the-ground-up/">Accessibility from the Ground Up</a> by Kitty Giraudel / <a href="https://interconnected.org/home/2021/08/12/notation">Collecting my thoughts about notation and user interfaces</a> by Matt Webb / <a href="https://benmyers.dev/blog/on-the-dl/">On the <code><dl></code></a>, Ben Myers on the underrated description list element</p>
<p><strong>Security.</strong> <a href="https://labs.detectify.com/2021/08/10/how-to-hack-apis-in-2021/">How to Hack APIs in 2021</a> by Hakluke and Farah Hawa</p>
<p><strong>Static Sitegen.</strong> I've recently used some of Mike Aparicio's <a href="https://www.11ty.recipes/">Eleventy recipes</a> to make some improvements around here (RSS feed, sitemap, etc.)</p>
<p><strong>Tools / Resources.</strong> make ZIP archives in the browser with <a href="https://stuk.github.io/jszip/">JSZip</a> / <a href="https://github.com/amirgamil/apollo">apollo</a> and <a href="https://github.com/thesephist/monocle">monocle</a>, two personal search engines / <a href="https://jlongster.com/future-sql-web">A future for SQL on the web</a>, James Long on persisting SQLite in IndexedDB / <a href="https://simoncozens.github.io/feature-tags/">OpenType feature database</a> / <a href="https://www.oilshell.org/blog/2021/08/xargs.html">An Opinionated Guide to <code>xargs</code></a> by Andy Chu</p>
<p><strong>Performance.</strong> <a href="https://paulfrazee.medium.com/building-on-budget-4b91b43d0357">Building on Budget</a>, Paul Frazee on the ROI of complex tooling / Atif Afzal warns: <a href="https://atfzl.com/don-t-attach-tooltips-to-document-body">Don't attach tooltips to <code>document.body</code></a> / <a href="https://nolanlawson.com/2021/08/15/does-shadow-dom-improve-style-performance/">Does shadow DOM improve style performance?</a> by Nolan Lawson / <a href="https://web.dev/lab-and-field-data-differences/">Why lab and field data can be different (and what to do about it)</a> by Philip Walton</p>
<p><strong>React / Web Components.</strong> <a href="https://github.com/thepassle/generic-components">generic-components</a>, a collection of generic web components with a focus on accessibility, and ease of use, by Pascal Schilp / <a href="https://blog.placemark.io/2021/06/08/react-aria.html">Components: <code>react-aria</code></a> by Tom MacWright</p>
<p><strong>Reading / Writing / Publishing.</strong> <a href="https://thesephist.com/posts/browser/">The web browser as a tool of thought</a> / <a href="https://jvns.ca/blog/confusing-explanations/">Patterns in confusing explanations</a> by Julia Evans (guilty on multiple counts) / <a href="https://diataxis.fr/">Diátaxis</a>, a systematic framework for technical documentation authoring</p>
<hr />
<p><strong>A CLI tip.</strong> One annoying thing to mark up in HTML is images along with their dimensions. Using <a href="https://imagemagick.org/script/identify.php">ImageMagick</a> you can grab an <code><img></code> tag right from the command line with:</p>
<pre class="language-bash"><code class="language-bash">magick identify <span class="token parameter variable">-format</span> <span class="token string">'<img src="%i" alt="" width="%w" height="%h"/>\n'</span> ./my-image.png<br /><br /><span class="token comment"># Outputs:</span><br /><span class="token comment"># <img src="./my-image.png" alt="" width="800" height="600"/></span></code></pre>
<p>Combined with <code>find</code> and <code>xargs</code> to produce markup for all the JPG files in a folder:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">find</span> <span class="token builtin class-name">.</span> <span class="token parameter variable">-type</span> f <span class="token parameter variable">-iname</span> <span class="token string">"*.jpg"</span> <span class="token operator">|</span> <span class="token function">xargs</span> <span class="token parameter variable">-L1</span> magick identify <span class="token parameter variable">-format</span> <span class="token string">"<img src='%i' alt='' width='%w' height='%h'/><span class="token entity" title="\n">\n</span>"</span></code></pre>
<p><strong>A thought.</strong> Jared Spool <a href="https://twitter.com/jmspool/status/1427802345140887552">on Twitter</a>:</p>
<blockquote>
<p>"Don't let the perfect be the enemy of the good." In UX design, this thinking delivers more damage than benefit. There's usually a LOT of distance between good and perfect. Good leaves room for better, which, when provided by someone else, leaves our designs behind.</p>
</blockquote>
<p><strong>A good game.</strong> <a href="https://www.playgoodsudoku.com/#why">Good Sudoku</a> on iOS is a novel take on the classic game.</p>
<hr />
<p><em>Link H/Ts</em>: <a href="https://simonwillison.net/">Simon Willison</a>, <a href="https://lobste.rs/">Lobste.rs</a></p>
wsf-xxv2021-08-11T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-08-11/<p><strong>CSS.</strong> Refactoring CSS, a series by Adrian Bece: <a href="https://www.smashingmagazine.com/2021/07/refactoring-css-introduction-part1/">Introduction</a> · <a href="https://www.smashingmagazine.com/2021/08/refactoring-css-strategy-regression-testing-maintenance-part2/">Strategy, Regression Testing And Maintenance</a> · <a href="https://www.smashingmagazine.com/2021/08/refactoring-css-optimizing-size-performance-part3/">Optimizing Size And Performance</a>¹ / <a href="https://www.joshwcomeau.com/css/transforms/">The world of CSS transforms</a>, a tutorial by Josh W. Comeau / <a href="https://shadows.brumm.af/">Smooth Shadow</a>, a tool by Philipp Brumm to create a layered CSS <code>box-shadow</code></p>
<p><strong>JavaScript / Web Components / React.</strong> <a href="https://www.smashingmagazine.com/2021/08/build-resilient-javascript-ui/">How To Build Resilient JavaScript UIs</a> by Callum Hart / <a href="https://webreflection.medium.com/about-web-components-cc3e8b4035b0">About Web Components</a>, a history by Andrea Giammarchi /
<a href="https://nolanlawson.com/2021/08/03/handling-properties-in-custom-element-upgrades/">Handling properties in custom element upgrades</a> by Nolan Lawson / <a href="https://www.smashingmagazine.com/2021/08/react-children-iteration-methods/">React Children And Iteration Methods</a> by Arihant Verma / <a href="https://css-tricks.com/three-buggy-react-code-examples-and-how-to-fix-them/">Three Buggy React Code Examples and How to Fix Them</a> by Hugh Haworth</p>
<p><strong>Accessibility.</strong> <a href="https://css-tricks.com/a-deep-dive-on-skipping-to-content/">A Deep Dive on Skipping to Content</a> by Paul Ratcliffe / <a href="https://www.smashingmagazine.com/2021/07/accessible-dialog-from-scratch/">Creating An Accessible Dialog From Scratch</a> by Kitty Giraudel</p>
<p><strong>Performance.</strong> <a href="https://vfoley.xyz/reasonable-use/">Making Reasonable Use of Computer Resources</a> / <a href="https://nolanlawson.com/2021/08/08/improving-responsiveness-in-text-inputs/">Improving the responsiveness in text inputs</a> by Nolan Lawson / <a href="https://web.dev/vitals-tools-workflow/">A performance-focused workflow based on Google tools</a> by Antoine Bisch and Garima Mimani</p>
<p><strong>Tools / Resources.</strong> Moved some of my projects to <a href="https://esbuild.github.io/">esbuild</a>, loving it so far; it feels knowable and <a href="https://esbuild.github.io/plugins/">making plugins</a> for it is a breeze / A first release candidate for <a href="https://v2.parceljs.org/blog/rc0/">Parcel 2</a> was just released / <a href="https://useful-forks.github.io/">useful-forks</a> and <a href="https://techgaun.github.io/active-forks/">active-forks</a>, two tools to find the most active forks of a GitHub repository / <a href="https://realdougwilson.com/writing/coding-with-character">Coding with character</a> by Doug Wilson / <a href="http://opentypecookbook.com/">Open Type Cookbook</a>, an introduction to OpenType features / <a href="https://octo.github.com/projects/repo-visualization">Visualizing a codebase</a> by Amelia Wattenberger / <a href="https://explog.in/notes/devtools/index.html">Building Developer Tools</a>, a collection of notes by Kunal Bhalla</p>
<p><strong>Parsing things.</strong> <a href="https://joshondesign.com/2021/07/16/ohm_markdown_parser">Make a Markdown Parser with OhmJS</a> by Josh Marinacci</p>
<p><strong>Reading / Writing / Publishing.</strong> <a href="https://journal.stuffwithstuff.com/2021/07/29/640-pages-in-15-months/">640 Pages in 15 Months</a>, Robert Nystrom on preparing a physical version of his <a href="http://craftinginterpreters.com/">Crafting Interpreters</a> book / <a href="https://buymusic.club/">Buy Music Club</a>, a place for creating and browsing lists of independent music purchasable on Bandcamp</p>
<p><strong>Creative coding.</strong> <a href="https://github.com/LingDong-/fishdraw">Procedurally generated fish</a> by Lingdong Huang / <a href="https://constraint.systems/">Constraint Systems</a>, a collection of experimental web-based creative tools by Grant Custer</p>
<hr />
<h3>Today I learned</h3>
<p><span aria-hidden="true">#</span> In Firefox Developer Tools, you can right-click an object logged to the console and choose the <span class="ui">Store as global variable</span> option. A definite improvement over doing <code>window.XXX = someObject</code> by hand throughout the code.</p>
<p><span aria-hidden="true">#</span> In Webpack, you can require an entire folder with <a href="https://webpack.js.org/guides/dependency-management/#context-module-api"><code>require.context()</code></a>. This came in handy for an icon catalog I was working on in <a href="https://storybook.js.org/">Storybook.js</a>:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// all-icons.js</span><br /><span class="token keyword">let</span> ctx <span class="token operator">=</span> require<span class="token punctuation">.</span><span class="token function">context</span><span class="token punctuation">(</span><span class="token string">'svgs'</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\.svg$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> ctx<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">k</span> <span class="token operator">=></span> <span class="token function">ctx</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><span aria-hidden="true">#</span> (via <a href="https://twitter.com/simevidas/status/1423972016630255628">Šime Vidas</a>) The <code>document.activeElement</code> is not always focused. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus"><code>Document.hasFocus()</code></a> method lets you confirm that it is.</p>
<hr />
<p>¹ Added Aug 31, 2021 after publishing.</p>
wsf-xxiv2021-08-02T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-08-02/<p><strong>The new CSS.</strong> <a href="https://alistapart.com/article/designing-for-the-unexpected/">Designing for the Unexpected</a> by Cathy Dutton in A List Apart / <a href="https://gridless.design/">gridless.design</a> by Donnie D'Amato / <a href="https://web.dev/new-responsive/">The new responsive</a>, web design in a component-driven world by Una Kravets / <a href="https://designnotes.blog.gov.uk/2021/07/07/making-links-easier-to-see-and-read-on-gov-uk/">Making links easier to see and read on gov.uk</a> with the new <code>text-decoration-*</code> properties, by Chris Ballantine-Thomas / <a href="https://medium.com/microsoft-design/leading-trim-the-future-of-digital-typesetting-d082d84b202"><code>leading-trim</code>: The Future of Digital Typesetting</a> by Ethan Wang / <a href="https://www.smashingmagazine.com/2021/04/guide-supported-modern-css-pseudo-class-selectors/">A Guide To Newly Supported, Modern CSS Pseudo-Class Selectors</a> by Stephanie Eckles / <a href="https://css-tricks.com/using-absolute-value-sign-rounding-and-modulo-in-css-today/">Using Absolute Value, Sign, Rounding and Modulo in CSS Today</a> by Ana Tudor</p>
<p><strong>Ye olde CSS.</strong> <a href="https://www.smashingmagazine.com/2021/07/css-absolute-units/">There Is No Such Thing As A CSS Absolute Unit</a> says Elad Shechter / <a href="https://allthingssmitty.com/2020/05/11/css-fix-for-100vh-in-mobile-webkit/">CSS fix for <code>100vh</code> in mobile WebKit</a>, a good summary by Matt Smith / <a href="https://blog.jim-nielsen.com/2021/things-i-learned-reading-webkits-ua-stylesheet/">Things I Learned Reading Webkit’s UA Stylesheet</a> by Jim Nielsen / <a href="https://ishadeed.com/article/button-label-alignment/">Aligning a Button Label Vertically</a> by Ahmad Shadeed</p>
<p><strong>UI / Accessibility.</strong> <a href="https://www.smashingmagazine.com/2017/07/designing-perfect-slider/">Designing The Perfect Slider</a> by Vitaly Friedman / Recents articles by Adrian Roselli: <a href="https://adrianroselli.com/2021/07/scroll-snap-challenges.html">Scroll Snap Challenges</a>, <a href="https://adrianroselli.com/2021/01/multi-function-button.html">Multi-Function Button</a>, <a href="https://adrianroselli.com/2021/07/stop-using-pop-up.html">Stop Using ‘Pop-up’</a> /
<a href="https://www.scottohara.me//blog/2021/07/16/section.html">Accessibility of the <code>section</code> element</a> by Scott O'Hara / <a href="https://www.tpgi.com/enough-with-the-role-play-lets-get-back-to-the-basics/">Enough with the role-play</a> by Ian Lloyd / <a href="https://dl.acm.org/doi/10.1145/2598510.2598600">What does it mean for a system to be useful?</a>, from 2014: “HCI has always focused on designing useful and usable interactive systems, but usability has dominated the field while research on usefulness has been largely absent.” / <a href="https://bkardell.com/blog/DesignAffordanceControls.html">Design Affordance Controls</a> by Brian Kardell / <a href="https://modalzmodalzmodalz.com/">MODALZ MODALZ MODALZ</a>, a fun guide to alternatives for the oft-maligned pattern / <a href="https://philipcdavis.com/writing/command-palette-interfaces">Command Palette Interfaces</a> by Philip Davis / <a href="https://melanie-richards.com/blog/anchor-pos-use-cases/">Call for web dev feedback: anchored positioning use cases + requirements</a> by Melanie Richards / <a href="https://www.nngroup.com/articles/drag-drop/">Drag–and–Drop: How to Design for Ease of Use</a> by Page Laubheimer</p>
<p><strong>JavaScript / DOM.</strong> <a href="https://danlaush.biz/posts/exploring-dynamic-imports">Just-In-Time translations and code that writes itself</a>, exploring uses for dynamic imports in JavaScript, by Dan Laush / <a href="https://jakearchibald.com/2021/encoding-data-for-post-requests/">Encoding data for POST requests</a>, a roundup by Jake Archibald / <a href="https://web.dev/urlpattern/"><code>URLPattern</code> brings routing to the web platform</a> / <a href="https://www.smashingmagazine.com/2021/07/dynamic-header-intersection-observer/">Building A Dynamic Header With Intersection Observer</a> by Michelle Barker / <a href="https://modern-web.dev/">Modern Web</a>, guides and tools to work with the browser and avoid complex abstractions, akin to <a href="https://open-wc.org/">Open Web Components</a> / <a href="https://github.com/syavorsky/comment-parser">comment-parser</a>, a generic JSDoc-like comment parser / <a href="https://nolanlawson.com/2021/08/01/why-its-okay-for-web-components-to-use-frameworks/">Why it’s okay for web components to use frameworks</a> by Nolan Lawson / <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types">Recommended Drag Types</a> in the MDN docs / Patrick H. Lauke's research on touch and pointer events: <a href="https://patrickhlauke.github.io/touch/">tests & demos</a>, <a href="https://patrickhlauke.github.io/touch/tests/results/">results</a>, and <a href="https://patrickhlauke.github.io/getting-touchy-presentation/">a presentation</a></p>
<p><strong>React.</strong> <a href="https://github.com/tannerlinsley/react-virtual">react-virtual</a>, hooks for virtualizing scrollable elements in React / <a href="https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/">Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior</a> by Mark Erikson / From the React 18 working group: <a href="https://github.com/reactwg/react-18/discussions/70">Concurrent React for Library Maintainers</a>, <a href="https://github.com/reactwg/react-18/discussions/46">Glossary + ELI5</a></p>
<p><strong>Performance, Web Vitals edition.</strong> Quick recap: LCP = Largest Contentful Paint, FID = First Input Delay, CLS = Cumulative Layout Shift / <a href="https://web.dev/learn-web-vitals/">From the horse's mouth</a> / <a href="https://www.smashingmagazine.com/2021/04/humble-img-element-core-web-vitals/">The Humble <code><img></code> Element And Core Web Vitals</a> by Addy Osmani / <a href="https://nicj.net/cumulative-layout-shift-in-practice/">CLS in Practice</a> by Nic Jansma / <a href="https://calibreapp.com/blog/cumulative-layout-shift">CLS: Measure and Avoid Visual Instability</a> by Karolina Szczur / <a href="https://web.dev/telegraph/">Improving CLS at Telegraph Media Group</a> by Chris Boakes / Barry Pollard has written extensively on the topic: <a href="https://www.smashingmagazine.com/2021/04/complete-guide-measure-core-web-vitals/">An In-Depth Guide To Measuring Core Web Vitals</a>, <a href="https://www.smashingmagazine.com/2021/06/how-to-fix-cumulative-layout-shift-issues/">How To Fix CLS Issues</a>, <a href="https://www.smashingmagazine.com/2021/05/reduce-font-loading-impact-css-descriptors/">A New Way To Reduce Font Loading Impact: CSS Font Descriptors</a></p>
<p><strong>Performance, otherwise.</strong> <a href="https://jakearchibald.com/2021/serving-sharp-images-to-high-density-screens/">Halve the size of images by optimising for high density displays</a> by Jake Archibald / <a href="https://amio.github.io/embedded-google-fonts/">Embedded Google Fonts</a>, a tool to embed font subsets in CSS / <a href="https://blog.jim-nielsen.com/2021/conditional-style-loading-not-so-fast/">Conditional Style Loading? Not So Fast</a> by Jim Nielsen, on how <code><link media='...'></code> can't prevent a stylesheet from being fetched; on the other hand, the technique lends itself to <a href="https://www.filamentgroup.com/lab/load-css-simpler/">loading CSS asynchronously</a> / <a href="https://github.com/sindresorhus/mem">mem</a>, a function memoization library by Sindre Sorhus</p>
<p><strong>Browsers.</strong> <a href="https://darker.ink/writings/Towards-richer-colors-on-the-Web">Towards richer colors on the Web</a> by Felipe Erias / <a href="https://developer.chrome.com/blog/renderingng-architecture/">Overview of the RenderingNG architecture</a> by Chris Harrelson / <a href="https://infrequently.org/2021/07/hobsons-browser/">Hobson's Browser</a> by Alex Russell</p>
<p><strong>Engineering.</strong> <a href="https://missing.csail.mit.edu/">The Missing Semester of Your CS Education</a>, because proficiency with tools is often overlooked / <a href="https://maryrosecook.com/blog/post/git-from-the-inside-out">Git from the inside out</a> by Mary Rose Cook / New vocab: <em>PAGNI</em> (Probably Are Gonna Need It) according to <a href="https://lukeplant.me.uk/blog/posts/yagni-exceptions/">Luke Plant</a>, <a href="https://simonwillison.net/2021/Jul/1/pagnis/">Simon Willison</a>, <a href="https://jacobian.org/2021/jul/8/appsec-pagnis/">Jacob Kaplan-Moss</a> / <a href="https://blog.syncinc.so/events-not-webhooks">Give me <code>/events</code>, not webhooks</a> by Anthony Accomazzo / <a href="https://simonwillison.net/2021/Jul/28/baked-data/">The Baked Data architectural pattern</a>, i.e. “bundling a read-only copy of your data alongside the code for your application, as part of the same deployment” by Simon Willison / <a href="https://josephg.com/blog/crdts-go-brrr/">CRDTs go brrr</a> by Seph Gentle</p>
<p><strong>Tools & Resources.</strong> <a href="https://allmaps.org/">Allmaps</a>, a set of open source tools that make it easier and more fun to search, explore, georeference and work with collections of digitised maps / <a href="https://github.com/launchlet/launchlet">launchlet</a>, customize the web with JavaScript and CSS / <a href="https://htmlrecipes.dev/">HTML Recipes</a> by Stephanie Eckles / <a href="https://a11y-tools.com/">a11y tools</a> by Ian Lloyd / <a href="https://w3c.github.io/predefined-counter-styles/">Ready-made CSS counter styles</a> for various cultures around the world, by the W3C / <a href="http://www.webaxe.org/web-accessible-code-library-design-systems-patterns/">Web Accessible Code Libraries and Design Patterns</a>, a great roundup</p>
<p><strong>Reading / Writing / Publishing.</strong> <a href="https://doctorow.medium.com/the-memex-method-238c71f2fb46">The Memex Method</a> by Cory Doctorow / <a href="https://jvns.ca/blog/2021/07/08/writing-great-examples/">Write good examples by starting with real code</a> says Julia Evans / <a href="https://wooorm.com/blog/hello-epub/">Hello, EPUB!</a>, a summary of the format by Titus Wormer</p>
<p><strong>Creative Coding.</strong> Libraries by Lingdong Huang: <a href="https://github.com/LingDong-/skeleton-tracing">skeleton-tracing</a>, a new algorithm for retrieving topological skeleton as a set of polylines from binary images; <a href="https://github.com/LingDong-/squiggy">squiggy</a>, a vector brushstroke library / <a href="https://www.nickwritesablog.com/drum-synthesis-in-javascript/">Drum Synthesis in JavaScript</a> by Nick Thompson /
Subscapes, a series by Matt DesLauriers <a href="https://mattdesl.substack.com/p/subscapes-part-1-preface">Part 1: Preamble</a>, <a href="https://mattdesl.substack.com/p/subscapes-part-2-traits">Part 2: Traits</a>, <a href="https://mattdesl.substack.com/p/subscapes-part-3">Part 3: Code</a></p>
<p><strong>Static sitegen / CMS.</strong> Andy Bell's <a href="https://piccalil.li/course/learn-eleventy-from-scratch/">Learn Eleventy from Scratch</a> (now on a <a href="https://learneleventyfromscratch.com/">dedicated website</a>) / <a href="https://giustino.blog/how-to-drafts-eleventy/">How to create drafts in Eleventy</a> by Giustino Borzacchiello / <a href="https://fvsch.com/static-site-generators">Static site generators</a> / <a href="https://github.blog/2021-06-22-framework-building-open-graph-images/">A framework for building Open Graph images</a> using Puppeteer by Jason Etcovich / Astro stuff: <a href="https://css-tricks.com/a-look-at-building-with-astro/">A Look at Building with Astro</a> by Chris Coyier, <a href="https://www.netlify.com/blog/2021/07/08/build-wicked-fast-sites-with-astro-an-introduction/">Build wicked fast sites with Astro</a> by Cassidy Williams / <a href="https://wordpress.org/news/2021/07/configuring-theme-design-with-theme-json/">Configuring Theme Design with <code>theme.json</code></a>, a neat addition to WordPress 5.8</p>
wsf-xxiii2021-07-11T00:00:00Zhttps://danburzo.ro/watchstarfork/2021-07-11/<p><em>This edition was stuck in draft-mode since 2020.</em></p>
<p><strong>Browsers.</strong> Asynchronous Clipboard API <a href="https://webkit.org/blog/10855/async-clipboard-api/">lands in Safari 13.1</a></p>
<p><strong>CSS.</strong> <a href="https://abandonedwig.info/blog/2020/07/03/css-painting-order.html">CSS Painting Order</a> by Martin Robinson</p>
<p><strong>Accessibility.</strong> <a href="https://www.paciellogroup.com/ux-series-universal-design-and-digital-accessibility/">Universal Design and Digital Accessibility</a> by Sarah Horton / <a href="https://www.a11yproject.com/">A11Y Project</a> has a new website</p>
<p><strong>Creative coding.</strong> <a href="http://danieltemkin.com/StraightenedTrees">Straightened Trees</a> by Daniel Temkin / <a href="https://vimeo.com/128375543">Pom Pom Mirror</a> by Daniel Rozin / <a href="http://roberthodgin.com/project/meander">Meander</a> by Robert Hodgin / <a href="https://github.com/karthik/wesanderson">Wes Anderson color palettes</a></p>
<p><strong>Performance & oopsies.</strong> <a href="https://www.ditdot.hr/en/causes-of-memory-leaks-in-javascript-and-how-to-avoid-them">Causes of memory leaks in JavaScript</a> and how to avoid them, by Ekaterina Vujasinović / <a href="https://nolanlawson.com/2020/02/19/fixing-memory-leaks-in-web-applications/">Fixing memory leaks in web applications</a> by Nolan Lawson / <a href="http://plasma-umass.org/BLeak/">BLeak</a> / <a href="https://jakearchibald.com/2020/events-and-gc/">Event listeners and garbage collection</a> by Jake Archibald</p>
<p><strong>CLI.</strong> <a href="https://git-scm.com/docs/git-grep">git grep</a> / <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> / <a href="https://jvns.ca/blog/2020/06/28/entr/">entr</a>, rerun your build when files change, by Julia Evans / <a href="https://twobitpreservation.com/blog/2020/6/11/why-use-the-command-line-for-digital-archiving-and-preservation">Why use the command line for digital archiving and preservation</a>, by Nicole Martin</p>
<p><strong>Computing.</strong> <a href="http://web.eecs.utk.edu/~azh/blog/teenytinycompiler1.html">Let's make a teeny tiny compiler</a>, a tutorial in three parts by Austin Z. Henley / <a href="https://malleable.systems/">Malleable Systems Collective</a> / <a href="https://futureofcoding.org/catalog/">The Whole Code Catalog</a> / <a href="https://we-make-money-not-art.com/can-you-design-a-website-on-a-very-limited-energy-budget-an-interview-with-gauthier-roussilhe/">Can you design a website on a (very) limited energy budget?</a>, an interview with Gauthier Roussilhe</p>
<p><strong>Music.</strong> <a href="https://www.68to05.com/">68to05</a> / <a href="https://www.mailta.pe/">mailta.pe</a> / <a href="https://driveandlisten.herokuapp.com/">Drive & Listen</a></p>
<p><em>Soundtrack: <a href="https://theburninghell.bandcamp.com/track/the-rich-stuff">Ariel Sharratt & Mathias Kom — The Rich Stuff</a></em></p>
wsf-xxii2019-11-20T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-11-20/<p><strong>Browsers.</strong> The update to macOS Catalina broke Firefox's ability to navigate links with the <kbd>Tab</kbd> key, but <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1587962">a fix is on its way</a>; on the upside, the small floating thumbnail that shows up in the bottom-right corner whenever you take a screenshot (<kbd>Cmd + Shift + 3</kbd>) can now be dropped in a web page across all browsers / <a href="https://briangrinstead.com/blog/firefox-webcomponents/">The Firefox UI is now built using Web Components</a> by Brian Grinstead</p>
<p><strong>JavaScript.</strong> <a href="https://pomb.us/build-your-own-react/">Build your own React</a> by Rodrigo Pombo</p>
<p><strong>Creative Coding.</strong> <a href="https://wwwtyro.net/2019/11/18/instanced-lines.html">Instanced line rendering</a> by Rye Terrell / <a href="https://waxy.org/2019/11/fast-and-free-music-separation-with-deezers-machine-learning-library/">Fast and Free Music Separation with Deezer’s Machine Learning Library</a> by Andy Baio</p>
<p><strong>Performance.</strong> <a href="https://www.ctrl.blog/entry/dns-prefetch-preconnect.html">What to <code><link rel=dns-prefetch></code> and when to use preconnect</a> by Daniel Aleksandersen</p>
wsf-xxi2019-11-06T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-11-06/<p><strong>Browsers.</strong> Starting with Firefox 72, notifications will only be allowed <a href="https://blog.mozilla.org/futurereleases/2019/11/04/restricting-notification-permission-prompts-in-firefox/">as a result of user interaction</a> / A business case for <a href="https://css-tricks.com/a-business-case-for-dropping-internet-explorer/">dropping Internet Explorer</a> by Ollie Williams /</p>
<p><strong>CSS.</strong> <a href="https://hacks.mozilla.org/2019/10/the-two-value-syntax-of-the-css-display-property/">The two-value syntax of the <code>display</code> property</a> by Rachel Andrew / <a href="https://ishadeed.com/article/unusual-use-cases-pseudo-elements/">Uncommon use-cases for pseudo-elements</a> by Ahmad Shadeed / <a href="https://www.smashingmagazine.com/2019/01/how-to-learn-css/">How to learn CSS</a> by Rachel Andrew / <a href="https://www.smashingmagazine.com/2019/11/css-things-cant-yet-do/">Things we can’t (yet) do in CSS</a> by Rachel Andrew / <a href="https://hacks.mozilla.org/2019/10/faster-layouts-with-css-grid-and-subgrid/">Faster layouts with CSS Grid (and Subgrid!)</a> by Miriam Suzanne / <a href="https://rwt.io/typography-tips/variable-fonts-what-web-authors-need-know">Variable Fonts: What web authors need to know</a> by Jason Pamental / <a href="https://www.chenhuijing.com/blog/box-alignment-and-overflow/">Box alignment and overflow</a> by Chen Hui Jing</p>
<p><strong>JavaScript.</strong> <a href="https://inventingwithmonster.io/20190207-break-the-rules-of-react-hooks/">How to break the rules of React hooks</a> by Matt Perry</p>
<p><strong>UX / Accessibility.</strong> <a href="https://asktog.com/atc/principles-of-interaction-design/">First Principles of Interaction Design</a> by Bruce Tognazzini / <a href="https://www.htmhell.dev/">HTMHell</a> by Manuel Matuzović / JavaScript isn’t always available <a href="https://adamsilver.io/articles/javascript-isnt-always-available-and-its-not-the-users-fault/">and it’s not the user’s fault</a> by Adam Silver / <a href="https://blog.whatwg.org/focusing-on-focus">Focusing on focus</a> by Rakina Zata Amni / <a href="https://adrianroselli.com/2019/10/accessible-drop-caps.html">Accessible Drop Caps</a> by Adrian Roselli / <a href="https://designsmarts.co/the-problem-with-dropdowns/">The problems with dropdown fields</a>, and what to use instead, by Josh Wayne / <a href="https://spectrum.adobe.com/">Spectrum</a>, Adobe's design system / <a href="https://www.rdsaunders.co.uk/accessibility/2019/10/24/accessibility-testing-tools.html">Accessibility testing tools</a> by Richard Saunders / <a href="https://nolanlawson.com/2019/11/05/what-ive-learned-about-accessibility-in-spas/">What I’ve learned about accessibility in SPAs</a> by Nolan Lawson / <a href="https://hacks.mozilla.org/2019/10/auditing-for-accessibility-problems-with-firefox-developer-tools/">Auditing for accessibility problems with Firefox Developer Tools</a> by Marco Zehe / <a href="https://adrianroselli.com/2019/10/stop-giving-control-hints-to-screen-readers.html">Stop giving control hints to screen readers</a> by Adrian Roselli</p>
<p><strong>CMS / Semantic Web.</strong> <a href="https://doriantaylor.com/the-symbol-management-problem">The symbol management problem, or: why I (still) use Semantic Web technology</a> by Dorian Taylor / <a href="https://www.smashingmagazine.com/2019/10/create-once-publish-everywhere-wordpress/">“Create once, publish everywhere” with WordPress</a> by Leonardo Losoviz / <a href="https://www.billerickson.net/reusable-blocks-accessible-in-wordpress-admin-area/">Reusable Blocks accessible in WordPress admin area</a> by Bill Erickson / <a href="https://www.smashingmagazine.com/2019/10/bookmarking-application-faunadb-netlify-11ty/">Create a bookmarking application with FaunaDB, Netlify and Eleventy</a></p>
<p><strong>Performance.</strong> <a href="https://philipwalton.com/articles/cascading-cache-invalidation/">Cascading cache invalidation</a> by Philip Walton</p>
<p><strong>Making software.</strong> <a href="https://daringfireball.net/linked/2019/11/01/van-rossum-clever-code">‘Maintainable code is more important than clever code’</a>, via John Gruber / <a href="https://www.sohamkamani.com/blog/how-to-write-good-documentation/">How to write good documentation</a>, and its essential elements, by Soham Kamani</p>
<p><strong>Creative Coding.</strong> <a href="https://music.arts.uci.edu/dobrian/maxcookbook/">MAX cookbook</a> by Christopher Dobrian and his students / <a href="https://simblob.blogspot.com/2019/10/chaikin-curves.html">Chaikin Curves</a> by Amit Patel</p>
<hr />
<p><em>Soundtrack: A Winged Victory for the Sullen — The Undivided Five</em></p>
wsf-xx2019-10-22T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-10-22/<p><em>Was out of the loop for a little while — slowly catching up.</em></p>
<p><strong>Web platform.</strong> <a href="https://hacks.mozilla.org/2019/09/caniuse-and-mdn-compat-data-collaboration/">Caniuse now lists MDN data!</a> / <a href="https://www.caniemail.com/">Caniemail</a> is like Caniuse but for email / <a href="https://danielcwilson.com/blog/2019/09/huedini/">Animating a Hue around the Color Wheel with Houdini</a> by Dan Wilson</p>
<p><strong>Browsers.</strong> <a href="https://medium.com/@firt/iphone-11-ipados-and-ios-13-for-pwas-and-web-development-5d5d9071cc49">iPhone 11, iPadOS and iOS 13 for PWAs and web development</a> by Maximiliano Firtman / <a href="https://www.trysmudford.com/blog/chrome-local-overrides/">Chrome local overrides</a> / <a href="https://twitter.com/Una/status/1171100118306754561">Assorted devtools tips</a>, prompted by Una Kravets on Twitter</p>
<p><strong>UX / Accessibility.</strong> <a href="https://bbc.github.io/gel/">Guidance for developers building accessible websites based on BBC GEL</a> / <a href="https://adamsilver.io/articles/where-to-put-buttons-in-forms/">Where to put buttons on forms</a> and <a href="https://adamsilver.io/articles/the-problem-with-tooltips-and-what-to-do-instead/">The problem with tooltips</a> by Adam Silver / <a href="https://a11y.reviews/">Accessibilitsy Reviews</a> by Adrian Roselli / <a href="https://www.the-haystack.com/2019/09/20/thinking-vs-choosing/">Thinking vs choosing</a> by Stephen Hay</p>
<p><strong>CSS.</strong> <a href="https://www.filamentgroup.com/lab/scrollbars/">Two Browsers Walked Into a Scrollbar</a> by Zach Leatherman / <a href="https://wiki.csswg.org/faq#selectors-that-depend-on-layout">CSS FAQ: Selectors that Depend on Layout</a> — well, that settles it / <a href="https://ada.is//blog/2019/06/11/six-assumptions-which-could-break-your-website/">Six assumptions which could break your website</a> by Ada Rose Cannon / <a href="https://wiki.csswg.org/ideas/mistakes">Incomplete List of Mistakes in the Design of CSS</a> / <a href="https://hankchizljaw.com/wrote/keeping-it-simple-with-css-that-scales/">Keeping it simple with CSS that scales</a> by Andy Bell</p>
<p><strong>JavaScript.</strong> <a href="https://gist.github.com/mudge/eb9178a4b6d595ffde8f9cb31744afcf">A <code>useDebounce</code> React hook</a> / <a href="https://wattenberger.com/blog/react-hooks">Thinking in React hooks</a> by Amelia Wattenberger</p>
<p><strong>Deep dives.</strong> <a href="https://hsivonen.fi/string-length/">It’s Not Wrong that "🤦🏼♂️".length == 7</a> by Henri Sivonen</p>
<p><strong>Performance.</strong> <a href="https://www.filamentgroup.com/lab/5g/">5G Will Definitely Make the Web Slower, Maybe</a> by Scott Jehl</p>
<p><strong>Parting thought.</strong> <a href="https://gerrymcgovern.com/digital-is-garbage/">Digital is garbage</a> by Gerry McGovern</p>
<p><strong>Toolbox.</strong> <a href="https://www.python.org/doc/sunset-python-2/">Python 2 is going away</a>. To start a web server with python 3, do <code>python -m http.server</code> instead of <code>python -m SimpleHTTPServer</code> / <a href="https://medium.com/@WebReflection/about-my-web-libraries-9b1e179d134">About my web libraries</a> by Andrea Giammarchi / <a href="https://github.com/HarryStevens/geometric">geometric</a> by Harry Stevens, with assorted <a href="https://observablehq.com/collection/@harrystevens/geometric">Observable notebooks</a> / <a href="https://github.com/usnationalarchives/digital-preservation">NARA digital preservation file format risk analysis and preservation plans</a> / <a href="https://medium.com/storybookjs/storybook-5-2-794958b9b111">Storybook 5.2 is out!</a></p>
<p><strong>Creative coding.</strong> <a href="https://cloudflare.design/color/thinking">Thinking about color</a> by the Cloudflare design team / <a href="https://tashian.com/articles/dynamicland/">At Dynamicland, The Building Is The Computer</a> by Carl Tashian</p>
<p><strong>(Work)life.</strong> <a href="https://stackingthebricks.com/why-blacksmiths-are-better-at-startups-than-you/">Why Blacksmiths are Better at Startups than You</a> by Amy Hoy / <a href="https://medium.com/@samo.burja/the-youtube-revolution-in-knowledge-transfer-cb701f82096a">The YouTube Revolution in Knowledge Transfer</a> by Samo Burja</p>
<hr />
<h3>Today I learned</h3>
<p>I started thinking about some little <a href="https://github.com/danburzo/css-challenges">CSS challenges</a>. First one, building a <a href="https://dev.to/danburzo/a-css-only-layout-debugger-1o9j">CSS-only element inspector</a>.</p>
<p>The CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior"><code>overscroll-behavior: none</code></a> declaration prevents navigation away from the page when you scroll elements with a two-finger swipe. <a href="https://dev.to/danburzo/css-micro-tip-prevent-history-navigation-on-horizontally-scrolling-elements-3iil">Wrote about it here</a>.</p>
<hr />
<p><em>I am addicted to <a href="https://dinopoloclub.com/games/mini-metro/">Mini Metro</a></em></p>
wsf-xix2019-09-07T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-09-07/<p><strong>Browsers.</strong> <a href="https://developers.google.com/web/updates/2019/05/paint-holding">Paint Holding in Chrome</a>, reducing the flash of white on same-origin navigations / <a href="https://webkit.org/blog/8970/how-web-content-can-affect-power-usage/">How Web Content Can Affect Power Usage</a> by Benjamin Poulain & Simon Fraser</p>
<p><strong>Talks.</strong> <a href="https://mixitconf.org/2019/the-internet-in-2030">The internet in 2030</a> by Andre Staltz / <a href="https://www.hillelwayne.com/talks/what-we-know-we-dont-know/">What we know we don't know</a> by Hillel Wayne</p>
<p><strong>SSG / CMS / IndieWeb.</strong> <a href="https://sitejs.org/">Site.js</a>, by Aral Balkan and Laura Kalbag, who've just launched <a href="https://small-tech.org/about">Small Technology Foundation</a> / <a href="https://www.jvt.me/posts/2019/07/22/why-website/">Why I have a website and you should too</a>, by Jamie Tanna / <a href="https://www.filamentgroup.com/lab/build-a-blog/">Build your own blog from scratch using Eleventy</a> by Zach Leatherman / <a href="https://www.wpglossary.net/">WP Glossary</a> / <a href="https://www.andersnoren.se/teman/">WordPress themes</a> by Anders Norén / <a href="https://www.brendanschlagel.com/2019/09/01/weaving-a-public-web-or-why-dont-i-blog-more/">Weaving a public web, or, why don’t I blog more?</a> by Brendan Schlagel / <a href="https://www.smashingmagazine.com/2019/08/upcoming-wordpress-renaissance/">The (upcoming) WordPress renaissance</a> by Leonardo Losoviz / <a href="https://jvns.ca/blog/2019/09/06/how-to-put-an-html-page-on-the-internet/">How to put a HTML page on the internet</a> by Julia Evans / <a href="http://www.petecorey.com/blog/2019/08/05/embedding-react-components-in-jekyll-posts/">Embedding React components in Jekyll blog posts</a> by Pete Corey / <a href="https://remysharp.com/2019/09/05/offline-listings">Offline listings</a> by Remy Sharp</p>
<p><strong>CSS.</strong> <a href="https://www.chenhuijing.com/blog/learning-css-by-reading-specifications">Learning CSS by reading specs</a> and <a href="https://www.chenhuijing.com/blog/where-did-css-named-colours-come-from/">Where did CSS named colors come from?</a> by Chen Hui Jing / <a href="https://adrianroselli.com/2019/09/under-engineered-text-boxen.html">Under-engineered text boxen</a> by Adrian Roselli / Estelle Weyl's <a href="https://estelle.github.io/CSS/">CSS in depth</a> / <a href="https://andy-bell.design/wrote/create-a-responsive-grid-layout-with-no-media-queries-using-css-grid/">Create a responsive grid layout with no media queries, using CSS Grid</a> by Andy Bell / <a href="https://www.smashingmagazine.com/2019/09/overflow-data-loss-css/">Overflow and data loss in CSS</a> by Rachel Andrew / <a href="https://www.smashingmagazine.com/2019/09/inspired-design-decisions-alexey-brodovitch/">Inspired Design Decisions: Alexey Brodovitch</a> by Andy Clarke / Jeremy Keith has put together some links for <a href="https://adactio.com/journal/15782">starting out in web development</a></p>
<p><strong>UX / Accessibility.</strong> <a href="https://davatron5000.github.io/a11y-nutrition-cards/">Nutrition cards for accessible components</a> by Dave Rupert / <a href="https://robdodson.me/command-palettes/">Command palettes for the web</a> by Rob Dodson / <a href="https://johnpalmer.site/#/spatialinterfaces">Spatial Interfaces</a> by John Palmer / <a href="https://kwokchain.com/2019/08/16/the-arc-of-collaboration/">The Arc of Collaboration</a> by Kevin Kwok / <a href="https://daneden.me/2019/08/27/where-we-can-go/">Where We Can Go</a>, Dan Eden on design systems / <a href="https://www.smashingmagazine.com/2019/08/bottom-navigation-pattern-mobile-web-pages/">Bottom Navigation Pattern On Mobile Web Pages: A Better Alternative?</a>, by Arthur Leonov</p>
<p><strong>JavaScript.</strong> <a href="https://www.figma.com/blog/how-we-built-the-figma-plugin-system/">How to build a plugin system on the web</a> and also sleep well at night, by the Figma team / <a href="https://philipwalton.com/articles/using-native-javascript-modules-in-production-today/">Using native JavaScript modules in production today</a> by Philip Walton / <a href="https://css-tricks.com/going-buildless/">Going Buildless</a> by Pascal Schilp / <a href="https://docs.google.com/presentation/d/1PUvpXMBEDS45rd0wHu6tF3j_8wmGC6cOLtOw2hzU-mw">Metaphysics and JavaScript</a>, slides by Rich Harris; responses <a href="https://blog.jim-nielsen.com/2019/thoughts-on-rich-harris-talk/">by Jim Nielsen</a> and <a href="https://gist.github.com/sebmarkbage/a5ef436427437a98408672108df01919">Sebastian Markbåge</a></p>
<p><strong>Creative Coding.</strong> <a href="https://www.c82.net/blog/?id=79">Making of Byrne's Euclid</a> by Nicholas Rougeux / <a href="https://bjango.com/articles/processingperfectloops/">Perfect loops in Processing</a> by Marc Edwards / <a href="https://wattenberger.com/blog/d3">How to learn D3.js</a> by Amelia Wattenberger / <a href="https://observablehq.com/@tmcw/enigma-machine">Enigma Machine</a> an Observable visualization by Tom MacWright / <a href="http://www.allenchou.net/2019/08/trigonometry-basics-sine-cosine/">Trigonometry basics: sine and cosine</a> by Allen Chou / <a href="https://otherorders.net/">Other orders</a> by Sam Lavigne / <a href="https://betterexplained.com/articles/matrix-multiplication/">A programmer's intuition for matrix multiplication</a> by Kalid Azad</p>
<p><strong>Toolbox.</strong> <a href="https://github.com/sharkdp/pastel">pastel</a>, a command-line tool to generate, analyze, convert and manipulate colors by David Peter / <a href="https://github.com/niklasvh/css-line-break">css-line-break</a> implements the Unicode Line Breaking Algorithm / <a href="https://catonmat.net/cookbooks/curl">curl cookbook</a> by Peter Krumins / <a href="https://github.com/pahen/madge">madge</a>, a command-line tools to visualize the ES module dependencies in your project / <a href="https://npkill.js.org/">npkill</a>, an interactive CLI to delete <code>node_modules</code> folders from your disk</p>
<p><strong>Personal tech.</strong> <a href="https://github.com/Y2Z/monolith">monolith</a> lets you save web pages as a single HTML file, with embedded CSS, JS, and images. / <a href="http://jsomers.net/blog/dictionary">You're probably using the wrong dictionary</a> (2014) by James Somers, via Frank Chimero</p>
<p><strong>Writing.</strong> Ryan Singer's <a href="https://basecamp.com/shapeup">Shape Up</a> book is now available as a PDF; <a href="https://m.signalvnoise.com/how-i-wrote-shape-up/">here's how</a> he wrote it / <a href="https://css-tricks.com/advice-for-technical-writing/">Advice for technical writing</a> by Chris Coyer</p>
<p><strong>Comics and zines.</strong> <a href="https://howhttps.works/">How HTTPS works</a> and <a href="https://howdns.works/">How DNS works</a> / <a href="https://jvns.ca/blog/2019/09/01/ways-to-write-zines-without-fancy-tools/">How to write zines with simple tools</a> by Julia Evans</p>
<p><strong>Making software.</strong> <a href="https://victorzhou.com/blog/avoid-premature-optimization/">Avoid Premature Optimization</a> by Victor Zhou / <a href="https://localghost.dev/2019/09/everything-i-googled-in-a-week-as-a-professional-software-engineer/">Everything I googled in a week</a> as a professional software engineer, by Sophie Koonin / <a href="https://google.github.io/eng-practices/">Google Engineering Practices</a> goes into how to do code reviews / Martin Fowler's <a href="https://martinfowler.com/architecture/">Software Architecture Guide</a> / <a href="https://justinjackson.ca/build">How do you build something people want?</a> by Justin Jackson</p>
<h3>Today I Learned</h3>
<p><strong>Some ffmpeg things.</strong> I wanted to repeat a 1-second video to make a 20-second video. The command is:</p>
<pre class="language-bash"><code class="language-bash">fffmpeg <span class="token parameter variable">-stream_loop</span> <span class="token number">20</span> <span class="token parameter variable">-i</span> one-sec.mp4 twenty-sec.mp4</code></pre>
<p>But the TIL was that ffmpeg has had some bugs in this area, and after trying some alternative methods, it turns out all I needed to do was to upgrade to the latest version. It worked flawlessly.</p>
<p><strong>Other ffmpeg things.</strong> Brian Whitman shares a brilliant command to <a href="https://gist.github.com/bwhitman/5be2f905556a25145dbac74fe4080739">make a sound collage</a> from the sound in your iPhone live photos / Ashley Blewer & colleagues have compiled <a href="https://amiaopensource.github.io/ffmprovisr/">a guide to ffmpeg</a> / Here are <a href="https://danburzo.ro/toolbox/ffmpeg">ffmpeg recipes and notes I'm collecting</a></p>
<hr />
<p><em>Soundtrack: Lana del Rey — Norman Fucking Rockwell!</em></p>
wsf-xviii2019-08-22T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-08-22/<p><strong>Web platform.</strong> Keeping an eye on <a href="https://cssfromscratch.com/">Learn CSS from Scratch</a>, a new, work-in-progress book by Andy Bell / TIL that Safari is never going to implement <a href="https://github.com/w3c/webcomponents/issues/509#issuecomment-230700060">customized built-in elements</a></p>
<p><strong>Toolbox.</strong> <a href="https://antiboredom.github.io/p5.riso/">p5.riso</a> a p5.js library for generating files suitable for Risograph printing, by Sam Lavigne and Tega Brain / <a href="https://github.com/GoogleChromeLabs/quicklink">quicklink</a> makes navigation faster by prefetching links during idle time / Get a <a href="http://npm.anvaka.com/">visualization of npm dependencies</a> for any package, a tool by Andrei Kashcha / Doug Wilson is maintaining a <a href="https://docs.google.com/spreadsheets/d/1u4FEJpU6sCAV7nRw7Or3Ak_mWGlSvkZDAuxg3Cpl4Rw/edit#gid=0">spreadsheet of good open typefaces</a>; see also Chad Mazzola's <a href="https://beautifulwebtype.com/">Beautiful web type</a> / <a href="http://www.selapa.net/swatches/colors/fileformats.php">Color swatch file formats</a> by Olivier Berten; Devine Du Linvega proposes using SVG in <a href="https://github.com/hundredrabbits/Themes">Themes</a></p>
<p><strong>UI.</strong> Adrian Roselli on the <a href="http://adrianroselli.com/2019/08/basic-custom-control-requirements.html">basic requirements for custom UI controls</a> / <a href="https://reactjs.org/blog/2019/08/08/react-v16.9.0.html">React 16.9 is out</a>, and a <a href="https://reactjs.org/blog/2019/08/15/new-react-devtools.html">new suite of developer tools</a> too / (audio) Javan Makhmali <a href="https://devchat.tv/js-jabber/jsj-376-trix-a-rich-text-editor-for-everyday-wrtiting-with-javan-makhmali/">talks about Trix</a>, Basecamp's rich text editor</p>
<p><strong>Performance.</strong> <a href="https://nolanlawson.com/2019/08/14/browsers-input-events-and-frame-throttling/">Browsers, input events, and frame throttling</a> by Nolan Lawson / Image lazy-loading <a href="https://web.dev/native-lazy-loading">is available in Chrome 76</a> / Carter Sande observes that <a href="https://carter.sande.duodecima.technology/javascript-page-navigation/">browsers are pretty good at loading web pages, it turns out</a> / <a href="https://csswizardry.com/2019/08/making-cloud-typography-faster/">Making cloud.typography fast(er)</a>, an exercise by Harry Roberts</p>
<p><strong>Corpus.</strong> Turns out a lot of books published 1924-1963 are <a href="https://boingboing.net/2019/08/01/80pct-pd.html">secretly in the public domain</a>?!</p>
<p><strong>Making software.</strong> Alan Kay on <a href="https://www.quora.com/Experienced-programmers-and-computer-scientists-what-are-some-really-old-or-even-nearly-forgotten-books-you-think-every-new-programmer-should-read/answer/Alan-Kay-11">old computer science books</a> that are still good / <a href="https://twitter.com/kenpex/status/1159498286664785921">Neat debug visualizations</a>, a Twitter thread / <a href="https://github.com/18F/technology-budgeting/blob/master/handbook.md">De-risking custom technology projects</a>, a handbook by the 18F team / <a href="https://www.w3.org/International/articles/article-text-size.en">Text size in translation</a> (2007) by the W3C Internationalization Activity / <a href="https://github.com/goldbergyoni/javascript-testing-best-practices">JavaScript testing best practices</a>, a guide by Yoni Goldberg / <a href="https://www.csc.gov.sg/articles/how-to-build-good-software">How to build good software</a> by Li Hongyi / <a href="https://github.blog/2019-08-16-highlights-from-git-2-23/">Git 2.23</a> splits <code>git checkout</code>, a near-universally confusing command, into <code>git switch</code> and <code>git restore</code>.</p>
<hr />
<h3>Today I Learned</h3>
<p><strong><code>npx</code> things from GitHub.</strong> Turns out <code>npx</code> works with GitHub as well, and you can even specify a branch:</p>
<pre class="language-bash"><code class="language-bash">npx username/repository<span class="token comment">#branch</span></code></pre>
<p>This is great: if you're building a Node CLI, people can test experimental branches without you needing to publish them to npm. Speaking of <code>npx</code>, Rich Harris made <a href="https://github.com/Rich-Harris/degit"><code>degit</code></a>, a tool to pull the latest commit off a GitHub repo. It also supports branches, so you can run:</p>
<pre class="language-bash"><code class="language-bash">npx degit username/repository<span class="token comment">#branch</span></code></pre>
<p>and you'll get a snapshot of the repo in your current folder. As opposed to <code>git clone</code>-ing it, you don't get the entire Git history. This is useful for bringing in starter/template repositories.</p>
<p><strong>Bye-bye FTP, hello <code>rsync</code>.</strong> Small websites, e.g. WordPress blogs, can be published on cheap shared hosting. In the past, I'd used FTP to upload local changes to the server, and <a href="https://www.panic.com/transmit/">Transmit</a> Sync removed the most annoying parts. But cheap shared hosting comes with a palette of quirks and little recourse to fix them, and my last experience was the straw that broke the camel's back. The host seemed to crap out after a few Sync runs and stopped accepting FTP connections. I knew about <a href="https://en.wikipedia.org/wiki/Rsync">rsync</a> but never looked into it, but now I'm glad I did.</p>
<p>You'll need to look for cheap shared hosting that allows you to connect via SSH, but most seem to provide it these days.</p>
<p>I had already generated a SSH key pair for using with GitHub, so I copied the <em>public</em> key:</p>
<pre><code>cat ~/.ssh/id_rsa.pub | pbcopy
</code></pre>
<p>and added it to the hosting provider, in a dedicated section of the typically byzantine admin UI that varies from provider to provider.</p>
<p>I needed to add some config in the <code>~/.ssh/config</code> file to define how to connect to the SSH host, because it used a different port than the default <code>22</code>, but also because I wanted to include the <code>rsync</code> script on a public GitHub repo without exposing the details of the connection:</p>
<pre><code>Host myhost
Hostname mydomain.com
User myuser
Port 18765
</code></pre>
<p>With this in place, I could swap the whole <em>start up Transmit, connect to the server, synchronize the folders, wait 30 seconds, or more, for it to finish</em> ceremony with a shockingly-fast <code>rsync</code> command:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">rsync</span> <span class="token parameter variable">--archive</span> <span class="token parameter variable">--compress</span> <span class="token parameter variable">--progress</span> --exclude-from<span class="token operator">=</span><span class="token string">".rsync-exclude"</span> ./ myhost:public_html/path/to/destination</code></pre>
<p>A quick breakdown of the various flags:</p>
<ul>
<li><code>--archive</code> is a shortcut for a few things: copy all files recursively, and maintain their metadata (permissions, timestamps, etc.). It's "archive" in the sense of "backing up things" rather than "ZIP archive"</li>
<li><code>--compress</code> compresses files before transferring them</li>
<li><code>--progress</code> is described in the <code>rsync</code> manual as giving <em>a bored user something to watch</em>.</li>
<li><code>--exclude-from</code> specifies a file containing patterns for excluding files from the transfer. You'll probably want to ignore the <code>.git</code> folder, <code>node_modules</code>, and other things that don't make sense on the server. See a sample file below.</li>
<li><code>./</code> is the local path. Notice the trailing slash, which instructs <code>rsync</code> to copy the <em>contents</em> of the folder, rather than the folder itself.</li>
<li><code>myhost:public_html/path/to/destination</code> is the remote path. <code>rsync</code> knows the <code>HOST:PATH</code> pattern refers to SSH, and we've defined <code>myhost</code> host in the <code>~/.ssh/config</code> file.</li>
</ul>
<p>And here's the (abridged) <code>.rsync-exclude</code> file:</p>
<pre><code>/node_modules
/.git
</code></pre>
<p>You can read more about using <code>rsync</code> in these articles:</p>
<ul>
<li><a href="https://linuxize.com/post/how-to-transfer-files-with-rsync-over-ssh/">How to Transfer Files with Rsync over SSH</a></li>
<li><a href="https://linuxize.com/post/how-to-exclude-files-and-directories-with-rsync/">How to Exclude Files and Directories with Rsync</a></li>
</ul>
<p>The <code>man rsync</code> page is also quite informative.</p>
<p>So long, FTP! (For now, at least)</p>
<hr />
<p><em>Soundtrack: <a href="https://blanckmass.bandcamp.com/album/animated-violence-mild">Blanck Mass — Animated Violence Mild</a></em></p>
wsf-xvii2019-08-06T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-08-06/<h3>Today I learned</h3>
<h4>Yak shaving</h4>
<p>Recently I've made some updates to <a href="https://github.com/marceljs/marcel">Marcel</a>, my personal take on static site generators. I was fond of the elegance of the <a href="https://twig.symfony.com/">Twig</a> templating language and was looking for an equivalent. For JavaScript there's <a href="https://github.com/mozilla/nunjucks">Nunjucks</a> which is pretty great, but rather limited. With <a href="https://github.com/marceljs/nunjucks-embed">modest outcomes</a> in trying to understand its parser and extend it to make Nunjucks more like Twig, I'm thinking: <em>‘how hard can it be to implement a template language from scratch?’</em></p>
<p>(Greek choir goes mad at this point.)</p>
<p><em>‘Since I'm basically reinventing the SSG wheel with Marcel, I might as well go a level deeper into yak shaving, right? It's not like there's a deadline to this thing.’</em></p>
<p>I vacillated around a set of names — <em>feuille</em> (very Proustian but hard to write and pronounce), <em>enaml</em> (which turns out is the name of <a href="https://github.com/nucleic/enaml">something else</a>), <em>lagoml</em> ("just enough", but sort of not good enough), <em>fka-twig</em> (<em>coughs intently</em>), and finally the perfect name that is <a href="https://github.com/marceljs/sontag">Sontag</a>.</p>
<p>I'm working at the limits of my knowledge here, so it's a learning experience.</p>
<h4>Parsing JavaScript</h4>
<p>With Sontag, I was curious how much of template parsing you can offload to a JavaScript parser. With some light tokenizing of tags, comments, and expressions, and then evaluating expressions as JavaScript, turns out you can go <a href="https://github.com/marceljs/acorn-sontag">pretty far</a>. Tools that I found essential in the process: <a href="https://github.com/acornjs/acorn">acorn</a> to parse the JavaScript to an ECMAScript Abstract Syntax Tree, <a href="https://github.com/acornjs/acorn/tree/master/acorn-walk">acorn-walk</a> and <a href="https://github.com/estools/estraverse">estraverse</a> to walk the tree and swap nodes around, then finally <a href="https://github.com/davidbonnet/astring">astring</a> to turn the tree back into a string.</p>
<p>How do you evaluate JavaScript? <code>eval()</code> feels icky even for a language that ostensibly <a href="https://github.com/marceljs/sontag#a-note-on-security">treats security as a non-goal</a>. The slighly less bad, and more flexible, approach is to create a new function with a certain body, and then invoke it to get the result:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> fn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">return </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>some_expression<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// => result</span></code></pre>
<p>Where's <code>AsyncFunction</code> though? Unlike <code>Function</code>, the <code>AsyncFunction</code> constructor is not a global object. Instead, you'll need to pick it up from an actual async function's constructor:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> AsyncFunction <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">getPrototypeOf</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>constructor<span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> fn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AsyncFunction</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">return await </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>some_expression<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">await</span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// => result</span></code></pre>
<h4>Getting comfortable with regular expressions</h4>
<p>Regular expressions are brilliant for matching expressions whose vocabulary is rather contained. Robust regular expressions can become a bit hard to read and debug, though. I found <a href="https://regexr.com/">RegExr</a> to be quite helpful in authoring and understanding them. Some surprises:</p>
<p><strong><code>.+</code> (any character) does not match <code>\n</code> (newline) characters.</strong> Apparently this is <a href="https://2ality.com/2017/07/regexp-dotall-flag.html">a whole thing</a>, and I was momentarily excited there's a <code>/s</code> flag to address it, but it's not supported across the board yet. So naturally I reach for <code>[.\n]</code>, a.k.a. <em>any character or the newline character</em> (or so I thought). But,</p>
<p><strong>The <code>.</code> in <code>[.\n]</code> is just a dot</strong>. When inside a character set, <code>.</code> loses its special status. TIL.</p>
<p><strong>Supplanting <code>\s</code>.</strong> The <a href="http://xregexp.com/">xregexp</a> library implements the <code>/s</code> flag, and also allows you to name and annotate your capture groups, which seems useful, but I didn't end up using it (mental note for another day). In the interim I'm using <code>[^]+</code> (all characters except no character) instead of <code>.+</code>.</p>
<p><strong>The limits of regular expressions.</strong> It may be that regular expressions can't be the only mechanism to parse things when they become complicated, and you need to bring out the big guns. In a happy coincidence, Benedikt Deicke is writing on the AppSignal blog these days about implementing a templating language in Ruby, with posts on <a href="https://blog.appsignal.com/2019/07/02/ruby-magic-brewing-our-own-template-lexer-in-ruby.html">lexers</a> and <a href="https://blog.appsignal.com/2019/07/30/ruby-magic-ruby-templating-the-parser.html">parsers</a> so far, and the posts seem easy to follow.</p>
<hr />
<p><em>Soundtrack: Terror Danjah — Red Flag</em></p>
wsf-xvi2019-08-01T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-08-01/<p><strong>Web Platform.</strong> Chrome 66 has added support for <a href="https://developers.google.com/web/updates/2019/07/image-support-for-async-clipboard">images in the async clipboard API</a></p>
<p><strong>CSS.</strong> <a href="https://medium.com/@simurai/sizing-web-components-8f433689736f">Sizing web components</a> / The highly anticipated Every Layout book by Heydon Pickering and Andy Bell <a href="https://every-layout.dev/checkout/">is finally out</a> / <a href="https://andy-bell.design/wrote/progressive-overflow-management-with-a-scroll-track-utility/">A <code>.scroll-track</code> utility</a> by Andy Bell</p>
<p><strong>Toolbox.</strong> <a href="https://github.com/mobxjs/mobx-state-tree">mobx-state-tree</a> / <a href="https://github.com/swup/swup">swup</a>, a page transition library / <a href="https://wakamaifondue.com/">Wakamai Fondue</a> answers the question “What can my font do?” / <a href="https://devchecklists.com/web-quality-checklist/en/">Web quality checklist</a> / <a href="https://www.linuxfoundation.org/press-release/2019/01/mapzen-open-source-data-and-software-for-real-time-mapping-applications-to-become-a-linux-foundation-project/">Mapzen is now part of the Linux Foundation</a> and their pricing seems very reasonable</p>
<p><strong>UX / Accessibility.</strong> Sarah Gold on why <a href="https://www.projectsbyif.com/blog/make-cities-smarter-while-protecting-privacy-yes-its-possible/">Making cities smarter while protecting privacy</a> is possible / <a href="https://craigmod.com/essays/fast_software/">Fast Software, the Best Software</a> by Craig Mod / <a href="https://www.deque.com/blog/introduction-to-feed-role-attribute/">Introduction to the <code>feed</code> role attribute</a> by Suman Damera / <a href="http://adrianroselli.com/2019/02/avoid-default-field-validation.html">Avoid Default Field Validation</a> by Adrian Roselli / <a href="https://blog.sapegin.me/all/accessible-inline-list/">Accessible inline list with bullets between items</a> by Artem Sapegin / <a href="https://adactio.com/journal/15559">Principle</a> by Jeremy Keith</p>
<p><strong>UI Libraries / Web Components.</strong> <a href="https://daveceddia.com/svelte-intro/">Introduction to Svelte</a> by Dave Ceddia / <a href="https://blog.bitsrc.io/7-tools-for-developing-web-components-in-2019-1d5b7360654d">7 Tools for Developing Web Components in 2019</a> by Jonathan Saring / <a href="https://bitworking.org/news/2019/07/looking-back-on-five-years-of-web-components">Looking back on five years of web components</a> by Joe Gregorio</p>
<p><strong>Back-end.</strong> Daniel Vassallo on how he uses <a href="https://twitter.com/dvassallo/status/1154516910265884672">the good parts of AWS</a> (Twitter thread)</p>
<p><strong>Creative coding.</strong> Bartosz Ciechanowski has some in-depth articles on <a href="https://ciechanow.ski/alpha-compositing/">Alpha Compositing</a>, <a href="https://ciechanow.ski/color-spaces/">Color Spaces</a>, and (from a while back) <a href="https://ciechanow.ski/drawing-bezier-curves/">Drawing Bézier curves</a> / <a href="http://nodes.io/">nodes.io</a>: a JavaScript-based 2D canvas for computational thinking / <a href="https://heydon.github.io/beadz-drum-machine/">Beadz</a>, a drum machine by Heydon Pickering</p>
<p><strong>Knowledge work.</strong> <a href="https://github.com/hypotext/notation">hypotext/notation</a>, a
collection of quotes on notation design & how it affects thought.</p>
<hr />
<h3>Today I learned</h3>
<p><strong>Web Components in SVG</strong>... are not supported. Which makes sense, in retrospect, as the Custom Elements API is defined only in the HTML spec.</p>
<p><strong>Moving away from Gmail.</strong> I'm <a href="https://github.com/danburzo/au-revoir-gmail">taking notes here</a>.</p>
<p><strong>More image optimizations.</strong> Still <a href="https://twitter.com/scottjehl/status/1154424344388558848">from Scott Jehl</a>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>picture</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>source</span><br /> <span class="token attr-name">media</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>(max-width:40em)<span class="token punctuation">"</span></span><br /> <span class="token attr-name">srcset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7<span class="token punctuation">"</span></span><br /> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://via.placeholder.com/1500x1000.png<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>picture</span><span class="token punctuation">></span></span></code></pre>
wsf-xv2019-07-22T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-07-22/<p><strong>CSS.</strong> Firefox 68 now supports <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap">CSS Scroll Snap</a>! Rachel Andrew <a href="https://hacks.mozilla.org/2019/06/css-scroll-snap-updated-in-firefox-68/">with more details</a> / <a href="https://dev.to/evanminto/intrinsically-responsive-css-grid-with-minmax-and-min-1n55">Intrinsically Responsive CSS Grid with <code>minmax()</code> and <code>min()</code></a> by Evan Minto / <a href="https://www.hongkiat.com/blog/css-grid-layout-minmax/">How to use <code>minmax()</code></a> Anna Monus / <a href="https://medium.com/@vamptvo/pixels-vs-ems-users-do-change-font-size-5cfb20831773">Pixels vs EMs: Users <em>do</em> change font size</a> by Evan Minto / <a href="https://every-layout.dev/blog/multi-column-manipulation/">Multi-column manipulation</a> by Heydon Pickering</p>
<p><strong>JavaScript.</strong> <a href="https://yomguithereal.github.io/posts/lru-cache">Implementing an efficient LRU cache</a> by Guillaume Plique / <a href="https://overreacted.io/algebraic-effects-for-the-rest-of-us/">Algebraic effects for the rest of us</a> by Dan Abramov / <a href="https://dassur.ma/things/is-postmessage-slow/">Is <code>postMessage()</code> slow?</a> by Surma</p>
<p><strong>Toolbox.</strong> <a href="https://github.com/segmentio/in-eu">in-eu</a>, a <s>tiny</s> library for detecting whether the user is in the European Union, based on their timezone and browser language — clever! / <a href="https://github.com/turbolinks/turbolinks">turbolinks</a>, noted here because I spent an inordinate amount of time trying to remember the name</p>
<p><strong>React.</strong> <a href="https://auth0.com/blog/next-js-practical-introduction-for-react-developers-part-1/">Next.js Practical Introduction: Pages and Layout</a> by Dan Arias, the first part of a series / <a href="https://www.youtube.com/playlist?list=PLZVuD5_lqlbPJwwR2zRYDAyuCGrMqkH5N">Building a component library</a>, a livestream series by Sid Kshetrapal / <a href="http://bradfrost.com/blog/post/frontend-design-react-and-a-bridge-over-the-great-divide/">Front-end, React, and a bridge over the great divide</a> by Brad Frost</p>
<p><strong>Web Components.</strong> <a href="https://www.varvet.com/blog/the-importance-of-elegance/">The Importance of Elegance</a> by Johan Halse / <a href="https://github.com/matthewp/haunted">haunted</a>, React hooks API for web components</p>
<p><strong>CMS.</strong> <a href="http://jschof.com/gutenberg-blocks/using-parcel-as-a-build-tool-for-gutenberg-blocks/">Using Parcel.js as a build tool for Gutenberg blocks</a> by Jim Schofield / I made a Timber-based WordPress starter theme called <a href="https://github.com/forklor/lathe">Lathe</a></p>
<p><strong>UX / Accessibility.</strong> Marcy Sutton on what they learned from <a href="https://www.gatsbyjs.org/blog/2019-07-11-user-testing-accessible-client-routing/">user testing of accessible client-side routing techniques</a> / <a href="https://adamsilver.io/articles/form-design-from-zero-to-hero-all-in-one-blog-post/">Form design: from zero to hero all in one blog post</a> by Adam Silver</p>
<p><strong>Design.</strong> <a href="https://bjango.com/articles/testingforwidegamut/">Testing for wide gamut</a> on the Bjango blog / <a href="https://medium.com/related-works-inc/the-people-part-of-design-systems-a5b54eea24f4">The People Part of Design Systems</a> by Magera Moon</p>
<p><strong>Creative coding.</strong> <a href="https://vas3k.com/blog/computational_photography/">Computational Photography: from selfies to black holes</a> by Vasily Zubarev</p>
<p><strong>Fun.</strong> <a href="https://matthewrayfield.com/goodies/popup-trombone/">Popup Trombone</a> by Matthew Rayfield, a trombone you play by resizing the browser window</p>
<p><strong>Software development.</strong> Some thoughts on <a href="https://www.martinfowler.com/bliki/Yagni.html">Yagni</a> (You Ain't Gonna Need It) from Martin Fowler / The Original <a href="https://github.com/chrislgarry/Apollo-11">Apollo 11 Guidance Computer</a> is on GitHub!</p>
<hr />
<h3>Today I learned</h3>
<p><strong>Minimal patterns.</strong> I love Scott Jehl's thinking on making enhancements to bridge the gap to future browser functionality:</p>
<blockquote>
<p>Many of my favorite JavaScript patterns deliberately aim to one day become obsolete. That's not to say scripts built to last are inherently any worse. I just particularly love patterns that are designed to be a bridge to a future where they'll do nothing at all. — Scott Jehl (<a href="https://twitter.com/scottjehl/status/1152304480169398272">source</a>)</p>
</blockquote>
<p>His latest is a simple pattern for <a href="https://www.filamentgroup.com/lab/load-css-simpler/">loading CSS asynchronously</a>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span><br /> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span><br /> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>style.css<span class="token punctuation">"</span></span><br /> <span class="token attr-name">media</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>print<span class="token punctuation">"</span></span><br /> <span class="token special-attr"><span class="token attr-name">onload</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token keyword">this</span><span class="token punctuation">.</span>media<span class="token operator">=</span><span class="token string">'all'</span></span><span class="token punctuation">"</span></span></span><br /><span class="token punctuation">/></span></span></code></pre>
<p>This works because browsers load <em>print</em> stylesheets asynchronously (since, presumably, they're not needed for displaying the page in the browser). All that's left is to flip the switch to <code>media='all'</code> once the stylesheet loads. It has better browser support than <a href="https://web.dev/defer-non-critical-css">the alternative approach</a>, but you might also want to include a plain stylesheet when JavaScript is not enabled:</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- Full example --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span><br /> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span><br /> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>style.css<span class="token punctuation">"</span></span><br /> <span class="token attr-name">media</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>print<span class="token punctuation">"</span></span><br /> <span class="token special-attr"><span class="token attr-name">onload</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token keyword">this</span><span class="token punctuation">.</span>media<span class="token operator">=</span><span class="token string">'all'</span></span><span class="token punctuation">"</span></span></span><br /><span class="token punctuation">/></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>noscript</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>style.css<span class="token punctuation">"</span></span> <span class="token attr-name">media</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>all<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>noscript</span><span class="token punctuation">></span></span></code></pre>
<hr />
<p><em>Soundtrack: Four Tet — Dreamer</em></p>
wsf-xiv2019-07-12T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-07-12/<p><strong>CSS.</strong> <a href="https://www.smashingmagazine.com/2019/07/css-custom-properties-cascade/">CSS custom properties and the cascade</a> by Miriam Suzanne / <a href="https://www.youtube.com/watch?v=OtlGo48iTOk">CSS Line Layout (video)</a> by Elika J. Etemad, a.k.a. <code>fantasai</code> / <a href="https://blog.logrocket.com/why-you-should-use-css-env-9ee719ce0f24/">Why you should use <code>env()</code></a> by Harry Nicholls / a piece of web lore I learned about recently: <a href="https://stackoverflow.com/questions/8318911/why-does-html-think-chucknorris-is-a-color/">Why does HTML think <code>chucknorris</code> is a color?</a> / <a href="https://psuter.net/2019/07/07/z-index">This survey about <code>z-index</code> values</a> by Philippe Suter brings back fond memories / <a href="https://css-tricks.com/restricting-a-pseudo-element-to-its-parents-border-box/">Restricting a pseudo-element to its parent's <code>border-box</code></a> by Ana Tudor</p>
<p><strong>JavaScript.</strong> <a href="https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/">Tips for rolling your own lazy loading</a> by Phil Hawksworth / <code>WeakRef</code> <a href="https://v8.dev/features/weak-references">gets implemented in v8</a>, but it's a bit over my head / <a href="https://bellard.org/quickjs/">QuickJS</a>, a JavaScript engine by Fabrice Bellard</p>
<p><strong>SSG / CMS.</strong> <a href="https://www.ctrl.blog/entry/setup-webmention.html">Add webmention support to your website in 10 minutes</a>, by Daniel Aleksandersen /
<a href="https://colly.com/">Simon Collison</a> has a beautiful new personal website / <a href="https://programminghistorian.org/en/lessons/building-static-sites-with-jekyll-github-pages">Building a static website with Jekyll and GitHub pages</a> by Amanda Visconti for The Programming Historian</p>
<p><strong>Serverless.</strong> <a href="https://serverless.css-tricks.com/">The power of serverless</a> for front-end developers, by Chris Coyer / <a href="https://github.com/hauxir/imgpush">imgpush</a>, minimalist self-hosted image service for user submitted images in your app by Haukur Rósinkranz / <a href="https://github.com/jxnblk/contrast-swatch">contrast-swatch</a>, image microservice for color contrast information by Brent Jackson</p>
<p><strong>Accessibility.</strong> Scott O'Hara tackles <code><toast></code> in two articles: <a href="https://www.scottohara.me/blog/2019/07/08/a-toast-to-a11y-toasts.html">A toast to an accessible <code>toast</code></a>, and <a href="https://www.scottohara.me/blog/2019/07/10/the-output-element.html"><code>output</code>: HTML's native live region element</a> / <a href="https://bits.ashleyblewer.com/blog/2017/09/20/accessibility-and-archivability/">Accessibility and archivability</a> and <a href="https://bits.ashleyblewer.com/blog/2019/06/29/rsync-guis-power-control-design-and-decisions/">making a GUI for <code>rsync</code></a> by Ashley Blewer / <a href="http://uncaughtreferenceerror.com/a-crash-course-to-screenreaders-for-sighted-developers/">An Intro To Screen Reader Testing for Sighted Developers</a> by Jessica Jordan</p>
<p><strong>Progressive enhancement.</strong> <a href="https://www.gov.uk/service-manual/technology/using-progressive-enhancement">Building a resilient frontend</a> from GOV.UK / <a href="https://github.com/danburzo/web-ui-notes/blob/master/notes/cutting-the-mustard.md">Trenchant enhancement</a>, a thing I wrote</p>
<p><strong>Performance.</strong> <a href="https://calibreapp.com/blog/how-pagespeed-works/">How Google PageSpeed works</a> by Ben Schwarz / <a href="https://samsaccone.com/posts/why-is-my-webpack-build-slow.html">Why is my webpack build slow?</a> by Sam Saccone</p>
<p><strong>React / Web Components.</strong> A <a href="https://github.com/facebook/react/pull/15927"><code>useEvent</code></a> hook / <a href="https://www.youtube.com/watch?v=8Kc2REHdwnQ">Build your own React</a> (video) by Rodrigo Pombo / <a href="https://sghall.github.io/react-compound-slider/">react-compound-slider</a> by Steve Hall / <a href="https://dev.to/ionic/why-we-use-web-components-2c1i">Why we use Web Components</a> by Max Lynch / <a href="https://dev.to/rowan_m/you-spin-me-right-r-und-k32">You spin me right r⟳und</a> by Rowan Merewood / <a href="https://io-gui.dev/">io-gui</a>, a UI framework for JavaScript applications and custom elements by Aki Rodić</p>
<p><strong>OSS work.</strong> GitHub added support for <a href="https://github.blog/2019-07-01-mark-files-as-viewed/">marking files as viewed</a> in pull requests and it works a treat!</p>
<p><strong>Personal tech.</strong> Darius Kazemi on how to <a href="https://runyourown.social/">run your own social network</a> / <a href="https://devonzuegel.com/post/contemplating-calendars">Contemplating calendars</a> by Devon Zuegel</p>
<p><strong>Creative coding.</strong> Shirley Wu <a href="https://twitter.com/sxywu/status/1148653314638147585">asks on Twitter</a>: what are the uses of trigonometry in data visualization? choice anwers: <a href="https://en.wikipedia.org/wiki/Cosine_similarity">Cosine similarity</a>, <a href="https://winkervsbecks.github.io/mathematics-of-animation/#/">Mathematics of Animation</a> / <a href="https://lospec.com/palette-list">lospec pallette list</a>, a database of palettes for pixel art</p>
<p><strong>Theory.</strong> Dorian Taylor's <a href="https://doriantaylor.com/annual-program-2019">Annual Programme for 2019</a> / <a href="https://www.curbed.com/2019/7/11/20686495/pattern-language-christopher-alexander">Let Christopher Alexander design your life</a> by Alexandra Lange</p>
<p><strong>Methodology.</strong> <a href="http://www.toolboxtoolbox.com/">The Toolbox Toolbox</a> is a collection of card-based thinking tools / This new book from Basecamp, <a href="https://basecamp.com/shapeup">Shape Up</a>, sounds great / <a href="https://www.linkedin.com/pulse/how-create-collaborative-rapid-prototype-douglas-ferguson/">How to create a collaborative, rapid prototype</a> by Douglas Ferguson</p>
<p><strong>Design.</strong> <a href="https://material.io/design/communication/data-visualization.html">Data Visualization Guidelines</a> from Google Material / <a href="https://noti.st/sturobson/yc1gwN/design-systems-and-front-end-architecture">Design Systems and Front-End Architecture</a> by Stuart Robson / <a href="https://variablefonts.dev/">Variable Fonts for Developers</a>, resources curated by Mandy Michael</p>
<hr />
<h1>Keeping up</h1>
<p><em>Looked into a couple of things which were on my radar for a long time.</em></p>
<p><strong>WordPress Gutenberg.</strong> Tried a fresh WP install with the <a href="https://github.com/wordpress/gutenberg">Gutenberg editor</a> and now I think I see how it's going to change a lot about building and writing for WP websites. <a href="https://developer.wordpress.org/block-editor/">There's an official guide</a> for building custom blocks. It's a bit convoluted and weird from a JS-first perspective, but I can get used to it. CSS Tricks has a <a href="https://css-tricks.com/guides/learning-gutenberg/">series of tutorials</a>, and this article by Jerome Duncan seems <a href="https://bigbite.net/creating-advanced-block-gutenberg/">pretty on point</a> (but I haven't read it through).</p>
<p><strong>Web Components.</strong> Started implementing a few simple web components to get a feel for it. There's potential, there are limitations, all in all an excellent addition to the web platform. <a href="https://github.com/danburzo/webcomponents-recipes">I'm chronicling my experience here</a>.</p>
<p><strong><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"><code>WeakMap</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet"><code>WeakSet</code></a></strong>. Although these JavaScript data structures have been available across browsers since a while, I haven't looked into what they are, and what they're good for, until now. Prompted by only barely grokking <a href="https://v8.dev/features/weak-references">the recent v8 announcement</a>, I read up on them and, boy, <a href="http://fitzgeraldnick.com/2014/01/13/hiding-implementation-details-with-e6-weakmaps.html">this article</a> by Nick Fitzgerald about using <code>WeakMap</code> for information hiding is kind of blowing my mind. May this technique be useful for hiding details in Web Components? It warrants further investigation.</p>
<hr />
<p><strong>Quote of the day</strong> comes from <a href="https://news.ycombinator.com/item?id=18334898">an anecdote</a> about Roberto Ierusalimschy, the main author of the Lua programming language. When asked by a student whether they should make a pull request for a new feature, he replied:</p>
<blockquote>
<p>Yes, but I won't use your code. I love that people send me ideas, but I actually enjoy coding... so I will gladly take your suggestions, though I will write it myself.</p>
</blockquote>
<hr />
<p><em>Soundtrack: <a href="https://www.youtube.com/watch?v=QyZeJr5ppm8">Aldous Harding — The Barrel</a></em></p>
wsf-xiii2019-07-05T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-07-05/<p><strong>Web Platform.</strong> <a href="https://svgwg.org/svg2-draft/geometry.html">SVG geometry properties</a> can be used from CSS <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1383650">starting with Firefox 69</a> / An intro to <a href="https://florian.rivoal.net/talks/line-breaking/">Line breaking and related properties</a> from CSS Text, by Florian Rivoal (<a href="https://www.youtube.com/watch?v=hXP0M7Um1dI">video</a>) / <a href="https://www.impressivewebs.com/dom-element-dimensions-and-css-transforms/">DOM element dimensions and CSS transforms</a> by Louis Lazaris</p>
<p><strong>Toolbox.</strong> Use <a href="https://npmfs.com/">npmfs</a> to look inside npm packages / <a href="https://jayfreestone.github.io/priority-plus/">priority-plus</a>, a simple, IntersectionObserver-based solution for navigation bars / <a href="https://github.com/siddharthkp/bundlesize">bundlesize</a> keeps your bundle size in check / <a href="https://github.com/mafintosh/streamx">streamx</a>, an iteration of the Node.js core streams with a series of improvements</p>
<p><strong>SSG.</strong> <a href="https://tomcritchlow.com/2019/06/19/bookmarklets-static-sites/">Using bookmarklets to script static sites</a> by Tom Critchlow / <a href="https://hylia.website/">Hylia</a>, a simple starter kit for Eleventy by Andy Bell</p>
<p><strong>UX / Accessibility.</strong> <a href="https://matthewstrom.com/writing/delight-comes-last/">Delight comes last</a> by Matthew Ström / <a href="https://uxdesign.cc/api-design-is-ui-design-a-way-to-collaborative-handoff-3d31ff57bb1">API design is UI design</a> by Tridip Thrizu / <a href="https://w3c.github.io/apa/captcha/">Inaccessibility of CAPTCHA</a>, a W3C report</p>
<p><strong>Performance.</strong> <a href="https://csstriggers.com/">CSS Triggers</a> / <a href="https://web.dev/google-search-sw">Bringing service workers to Google Search</a> by Jeff Posnick / <a href="https://web.dev/virtualize-long-lists-react-window/">Virtualize large lists with <code>react-window</code></a> by Houssein Djirdeh and Jason Miller / <a href="https://v8.dev/blog/cost-of-javascript-2019">The cost of JavaScript in 2019</a> by Addy Osmani / <a href="https://medium.com/spring-media-techblog/how-we-achieved-the-best-web-performance-with-partial-hydration-20fab9c808d5">The case of partial hydration (with Next and Preact)</a> by Lukas Bombach</p>
<p><strong>Hardware.</strong> <a href="https://www.raspberrypi.org/blog/raspberry-pi-4-on-sale-now-from-35/">Raspberry Pi 4 has been released</a> and everybody's excited; <a href="https://news.ycombinator.com/item?id=20264911">some possible uses</a>, for inspiration / <a href="https://medium.com/@maxbraun/meet-accent-352cfa95813a">Let's make more calm technology</a> by Max Braun</p>
<p><strong>Explorables.</strong> <a href="https://learningsynths.ableton.com/">Learning Synths</a> from Ableton</p>
<p><strong>Creative coding.</strong> <a href="https://observablehq.com/@mbostock/working-with-wikipedia-data">Working with Wikipedia data</a> in Observable, by Mike Bostock / <a href="https://codepen.io/collection/DbzqqP">Swiss-style Typographical Posters</a> with CSS Grid, a CodePen collection by Henry Desroches / <a href="https://www.youtube.com/watch?v=8Uo6zFwSO78">Generative Machines with Matt DesLauriers</a> (video), a talk at FITC Toronto 2019</p>
<p><strong>Web Components:</strong> <em>not great, not terrible</em> / <a href="https://dev.to/richharris/why-i-don-t-use-web-components-2cia">Why I don't use Web Components</a> by Rich Harris; counterpoints by <a href="https://gist.github.com/WebReflection/71aed0c811e2e88e3cd3c647213f0e6c">Andrea Giammarchi</a>, <a href="https://dev.to/shihn/why-i-use-web-components-my-use-cases-1nip">Preet Shihn</a> / <a href="https://blog.almaer.com/the-truth-about-web-components-and-frameworks/">The truth</a> about web components and frameworks, by Dion Almaer / <a href="https://www.baldurbjarnason.com/weeknote-14/">Weeknote 14</a> by Baldur Bjarnason on his recent experience with Web Components / <a href="https://github.com/WebReflection/heresy">heresy</a>, React-like custom elements</p>
<p><strong>Collaborative editing.</strong> <a href="https://caolan.org/posts/collaborative_editor_in_rust.html">Collaborative Editor in Rust</a>, a prototype by Caolan McMahon / <a href="https://pierrehedkvist.com/posts/1-creating-a-collaborative-editor">Creating a collaborative editor</a> by Pierre Hedkvist / <a href="http://digitalfreepen.com/2017/10/06/simple-real-time-collaborative-text-editor.html">A simple approach</a> to building a real-time collaborative text editor by Rudi Chen / <a href="https://juretriglav.si/open-source-collaborative-text-editors/">Open source collaborative text editors</a> by Jure Triglav</p>
<p><strong>Making software.</strong> <a href="https://jvns.ca/blog/2019/06/23/a-few-debugging-resources/">How does debugging a program look like?</a> by Julia Evans / <a href="https://twitter.com/erinfranmc/status/1141732650123702273">Bracket pair colorizer</a> — such a nice idea, but struggling to find one for Sublime Text / Things that may (not) help with bugs: <a href="https://blog.acolyer.org/2017/09/19/to-type-or-not-to-type-quantifying-detectable-bugs-in-javascript/">static typing</a>, <a href="https://hal.inria.fr/hal-01653728/document">code coverage</a></p>
<p><strong>Cross-platform.</strong> <a href="https://arstechnica.com/gadgets/2019/07/catalyst-deep-dive-the-future-of-mac-software-according-to-apple-and-devs/">Catalyst deep dive</a>: the future of Mac software according to Apple and devs / <a href="https://jlongster.com/secret-of-good-electron-apps">The secret to good Electron apps</a> by James Long / <a href="https://wasmbyexample.dev/">WASM by example</a> by Aaron Turner</p>
<hr />
<h3>Things I learned</h3>
<p><strong>I made this.</strong> An old idea, finally realized: <a href="https://danburzo.github.io/webcolors-cmyk">CMYK Named Colors</a>. I thought I'd give Next.js a shot for this project. Deploying it to GitHub Pages turned out to be a bit fiddly, but other than that I'm pretty happy with the outcome. Server-side rendered React feels good and I think I'll use this setup for more projects.</p>
<p><strong>Position: sticky.</strong> It's also the first time I used the <code>position: sticky</code> CSS property for navigation — I guess it hadn't occurred to me it had <a href="https://caniuse.com/#feat=css-sticky">such wide support</a>? A minimal setup:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">header</span> <span class="token punctuation">{</span><br /> <span class="token property">position</span><span class="token punctuation">:</span> -webkit-sticky<span class="token punctuation">;</span> <span class="token comment">/* For Safari */</span><br /> <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span> <span class="token comment">/* For all the other browsers */</span><br /> <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token comment">/* Where to stick */</span><br /> <span class="token property">z-index</span><span class="token punctuation">:</span> 100<span class="token punctuation">;</span> <span class="token comment">/* Ensure proper stacking */</span><br /><span class="token punctuation">}</span></code></pre>
<blockquote>
<p>The <code>z-index</code> part is to ensure consistent stacking across browsers. I bumped into "incorrect" stacking in Firefox, but there seems to be <a href="https://github.com/w3c/csswg-drafts/issues/1053">a discussion around it</a></p>
</blockquote>
<p>A nice addition to this CSS property would be a <code>:stuck</code> pseudo-class selector, so you can style the element based on whether it's in its sticky state. <a href="https://github.com/w3c/csswg-drafts/issues/1656">There are good reasons</a> for why this doesn't exist in CSS, but you can roll your own <a href="https://developers.google.com/web/updates/2017/09/sticky-headers"><code>sticky-change</code> event</a> with IntersectionObserver.</p>
<p><strong>Inputs on iOS.</strong> Everybody's used to iOS Safari zooming into <code><select></code> elements when you use them. But why? Turns out it happens when the font size for the element falls below 16px. Being a fan of the <code>em</code> unit, I came up with this to fix it:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1em<span class="token punctuation">,</span> 16px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Curiously, Safari is the only current browser supporting <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max">the <code>max()</code> function</a>.</p>
<p>Speaking of inputs, I haven't looked into why <code><input type='range'/></code> is so capricious in iOS Safari. Sometimes it's very responsive, other times you struggle with grabbing the handle.</p>
<p><strong>The <code>formaction</code> attribute.</strong> You can have <a href="https://css-tricks.com/separate-form-submit-buttons-go-different-urls/">more than one submit button</a> in a HTML form, pointing to different URLs.</p>
<hr />
<p><em>Listening to: <a href="https://podcasts.apple.com/au/podcast/take-5/id1251358023?i=1000442258760">Take 5 with Warren Ellis</a></em></p>
wsf-xii2019-06-16T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-06-16/<p><strong>CSS.</strong> <a href="https://testdriven.io/blog/css-grid/">CSS Grid: No nonsense layouts</a> by Austin Johnston / <a href="https://every-layout.dev/">Every Layout</a>: Relearn CSS Layout, a project (and forthcoming book) by Heydon Pickering and Andy Bell / <a href="https://almonk.github.io/pylon/">pylon.css</a>, declarative flexbox by Alasdair Monk (see also <a href="https://rsms.me/raster/">raster</a> by Rasmus Andersson) / Rachel Andrew's <a href="http://csslayout.news/">CSS Layout News</a> has made it to 200 issues!</p>
<p><strong>SSG.</strong> <a href="https://minicomp.github.io/wax/">Wax</a>, a minimal computing project for making digital exhibitions with Jekyll; Alex Gil <a href="https://twitter.com/elotroalex/status/1136364277345017857">with more details</a> / <a href="http://archieml.org/">ArchieML</a> <em>was created at The New York Times to make it easier to write and edit structured text on deadline</em></p>
<p><strong>UI.</strong> <a href="https://sebastiandedeyne.com/react-for-vue-developers/">React for Vue developers</a> by Sebastian De Deyne</p>
<p><strong>UX.</strong> Two essays by Lennart Ziburski: <a href="https://desktopneo.com/">Desktop Neo</a> (2016), <a href="https://thecloudfall.com/">The Cloud Fall</a> (2019) / <a href="https://productexperienceplaybook.org/">The Product Experience Playbook</a> by Auttomatic</p>
<p><strong>Essays.</strong> <a href="https://jmkorhonen.net/2019/05/13/technology-in-a-post-growth-world-lessons-from-the-1970s-at-movement/">Technology in a Post-Growth World</a>, lessons from the 1970s Appropriate Technology movement</p>
<p><strong>Open Source.</strong> GitHub introduces <a href="https://github.blog/2019-06-06-generate-new-repositories-with-repository-templates/">template repositories</a> / <a href="https://liberamanifesto.com/">The Libera Manifesto</a>, a manifesto for open source and free services / <a href="https://github.com/kylelobo/The-Documentation-Compendium">The Documentation Compendium</a> by Kyle Lobo, templates & tips on writing high-quality documentation that people want to read</p>
<p><strong>Software Engineering.</strong> <a href="https://blog.juliobiason.net/thoughts/things-i-learnt-the-hard-way/">Things I Learnt The Hard Way (in 30 Years of Software Development)</a> by Julio Biason</p>
<p><strong>Web Components.</strong> <a href="https://github.com/skatejs/skatejs">skatejs</a> / <a href="https://github.com/tkent-google/std-switch">A standard <code>switch</code> form control</a> by Kent Tamura / <a href="https://adamsilver.io/articles/the-problem-with-web-components/">The Problem with Web Components</a> by Adam Silver; counterpoints by <a href="https://twitter.com/slightlylate/status/1138492244309172224">Alex Russell</a>, <a href="https://twitter.com/WebReflection/status/1138501634953269253">Andrea Giammarchi</a> / <a href="http://webcomponents.news/">Web Components News</a>, a newsletter by Andy Bell</p>
<p><strong>JavaScript / DOM.</strong> <a href="https://github.com/lydiahallie/javascript-questions">javascript-questions</a>, <em>a long list of (advanced) JavaScript questions</em> — I got some wrong! / <a href="https://exploringjs.com/impatient-js/">JavaScript for impatient programmers</a> by Dr. Axel Rauschmayer / James Padosley with <a href="https://twitter.com/padolsey/status/1137317009463619584">some delighful heresy</a> using iterators and generators / <a href="https://dassur.ma/things/when-workers/">When should you be using Web Workers?</a> by Surma</p>
<p><strong>Toolbox.</strong> <a href="https://github.com/IDMNYU/p5.js-func">p5.js-func</a>, function generation in the time, frequency, and spatial domains / <a href="https://github.com/pbeshai/d3-interpolate-path">d3-interpolate-path</a>, interpolate SVG <code>path</code> elements / <a href="https://github.com/davidtheclark/focus-trap-react">focus-trap-react</a>, a React component that traps focus / When I needed a barcode for a book, I used <a href="https://github.com/arthurattwell/isbn-barcode">isbn-barcode</a> to get a SVG file</p>
<p><strong>Knowledge work.</strong> <a href="https://github.com/gillkyle/sol-journal">Sol</a>: simple, personal journaling / <a href="https://github.com/mholt/timeliner">Timeliner</a>: all your digital life on a single timeline, stored locally / <a href="https://github.com/jarun/Buku">Buku</a>, a browser-independent bookmark manager</p>
<hr />
<p><strong>TIL.</strong> <a href="https://almonk.github.io/pylon/">pylon.css</a> got me thinking: are there any issues with using ad-hoc custom HTML elements like this? The HTML spec clarifies that a <code><myelement></code> (without a hyphen in its name) is interpreted as a <a href="https://html.spec.whatwg.org/multipage/dom.html#htmlunknownelement">HTMLUnknownElement</a> and, furthermore:</p>
<blockquote>
<p>User agents must treat elements and attributes that they do not understand as semantically neutral; leaving them in the DOM (for DOM processors), and styling them according to CSS (for CSS processors), but not inferring any meaning from them.</p>
</blockquote>
<p>So using custom elements for layout doesn't seem to be much worse than plain <code><div></code> elements.</p>
<hr />
<blockquote>
<p>We build our computer systems the way we build our cities: over time, without a plan, on top of ruins. — Ellen Ullman (<a href="https://twitter.com/devonzuegel/status/1137386992398766081">via</a>)</p>
</blockquote>
<p><em>Soundtrack: Burial — Claustro / State Forest</em></p>
wsf-xi2019-06-07T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-06-07/<p><strong>Web Platform.</strong> Safari 13 Beta <a href="https://developer.apple.com/documentation/safari_release_notes/safari_13_beta_release_notes">release notes</a>: Pointer Events are <a href="https://caniuse.com/#feat=pointer">finally good to go</a>, Safari gets the <a href="https://wicg.github.io/visual-viewport/">Visual Viewport API</a> (available in Chrome, and behind a flag in Firefox), programmatic paste on iOS, and the ability for PWAs to <a href="https://bugs.webkit.org/show_bug.cgi?id=185448">access the camera</a> / More on <a href="https://hacks.mozilla.org/2019/06/css-grid-level-2-subgrid-is-coming-to-firefox/">CSS Grid Level 2</a>, available in Firefox Nightly, by Rachel Andrew / <a href="https://mxb.dev/blog/the-css-mindset/">The CSS Mindset</a> by Max Böck</p>
<p><strong>The Commons.</strong> <a href="https://www.w3.org/2001/tag/doc/ethical-web-principles/">W3C TAG Ethical Web Principles</a> / <a href="https://github.com/ceejbot/economics-of-package-management/blob/master/essay.md">The Economics of Package Management</a> by C J Silverio / <a href="https://tinysubversions.com/notes/reading-activitypub/">A highly opinionated guide to learning about ActivityPub</a> by Darius Kazemi / <a href="https://nolanlawson.com/2019/05/31/tech-veganism/">Tech Veganism</a> by Nolan Lawson / <a href="https://gilest.org/bwrss.html">Black and white and RSS</a> by Giles Turnbull, a stream of black and white photographs, updating throughout June 2019, only available on RSS</p>
<p><strong>UI / Accessibility.</strong> <a href="https://twitter.com/vstguis">A Twitter bot</a> that tweets user interfaces from VST audio plugins / <a href="https://www.uibot.app/">uibot</a> by Janne Aukia, <em>an experiment on how far one could automate the generation of visual designs, what kinds of advantages it would lead to and what issues one would face</em> / <a href="https://www.scottohara.me/blog/2019/05/25/tabindex.html">Tabindex: it rarely pays to be positive</a> by Scott O'Hara</p>
<p><strong>SSG.</strong> <a href="https://thepaciellogroup.github.io/cupper/">Cupper</a>, a documentation builder / <a href="https://github.com/mathieudutour/medium-to-own-blog">medium-to-own-blog</a> by Mathieu Dutour / <a href="https://david.darn.es/tutorial/2019/06/01/use-eleventy-to-generate-a-ghost-blog/">Use Eleventy To Generate A Ghost Blog</a>, a tutorial by David Darnes on writing in Ghost but building with Eleventy.</p>
<p><strong>Workflows.</strong> <a href="http://www.petecorey.com/blog/2019/05/27/how-i-actually-write-my-first-ebook/">How I actually wrote my first ebook</a> by Pete Corey</p>
<p><strong>Creative coding.</strong> Anders Hoff has written about his recent generative work: <a href="https://inconvergent.net/2019/a-tangle-of-webs/">A tangle of webs</a>, <a href="https://inconvergent.net/2019/a-tangle-of-webs-3d/">A tangle of webs 3D</a>, <a href="https://inconvergent.net/2019/depth-of-field/">Depth of field</a> / <a href="https://github.com/luruke/awesome-casestudy">awesome-casestudy</a>, a list of technical case studies on WebGL and creative development / <a href="https://zalo.github.io/blog/constraints/">The essence of constraint is projection</a> by Johnathon Selstad</p>
<p><strong>Toolbox.</strong> <a href="https://zzz.dog/">Zdog</a> by David DeSandro, a round, flat, designer-friendly pseudo-3D engine for canvas & SVG / <a href="https://cssgrid-generator.netlify.com/">CSS Grid Generator</a> by Sarah Drasner / <a href="https://github.com/una/extra.css">extra.css</a> by Una Kravets (<a href="https://extra-css.netlify.com/">the demos</a> only work in Chrome so far); <a href="https://houdini.glitch.me/">more on Houdini</a></p>
<hr />
<h3>TIL</h3>
<p><strong>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append"><code>append()</code></a> DOM method</strong>, an alternative to <code>appendChild()</code> that lets you add many elements (and text nodes) at once.</p>
<h4>Intercepting "plain clicks"</h4>
<p>Problem: you have a bunch of links which are supposed to work like normal links. But, on a "plain click", you want to execute some equivalent JavaScript (think <em>turbolinks</em> or <em>pjax</em>). How do you do that in the most unobtrusive way?</p>
<p>A workable solution below. (Can it be improved?)</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">isPlainClick</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span><br /> <span class="token comment">// The primary button was clicked...</span><br /> e<span class="token punctuation">.</span>button <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">&&</span><br /> <span class="token comment">// ...and no modifier keys are present</span><br /> <span class="token operator">!</span>e<span class="token punctuation">.</span>ctrlKey <span class="token operator">&&</span><br /> <span class="token operator">!</span>e<span class="token punctuation">.</span>metaKey <span class="token operator">&&</span><br /> <span class="token operator">!</span>e<span class="token punctuation">.</span>shiftKey <span class="token operator">&&</span><br /> <span class="token operator">!</span>e<span class="token punctuation">.</span>altKey<span class="token punctuation">;</span><br /><br />el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isPlainClick</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">doStuff</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<hr />
<p><em>Soundtrack: Michelle Gurevich — Exciting Times</em></p>
wsf-x2019-05-27T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-05-27/<p><strong>Web platform.</strong> New in Firefox Nightly: subgrid (<a href="https://css-irl.info/subgrid-is-here/">Michelle Barker reports</a>), <a href="https://webplatform.news/issues/2019-05-17"><code>-webkit-line-clamp</code></a> / Panic <a href="https://twitter.com/panic/status/1106633444157607936">relates</a> the trepidations of working with extended gamut colors in CSS</p>
<p><strong>Open Source.</strong> <a href="https://m.signalvnoise.com/open-source-beyond-the-market/">Open Source beyond the market</a> by DHH / GitHub debuts <a href="https://github.com/sponsors">Sponsors</a></p>
<p><strong>JavaScript.</strong> How to use <code>reduce()</code> <a href="https://jrsinclair.com/articles/2019/functional-js-do-more-with-reduce/">for more than just numbers</a> by James Sinclair / <a href="https://medium.com/@bluepnume/functional-ish-javascript-205c05d0ed08">Functional-ish JavaScript</a> by Daniel Brain</p>
<p><strong>CMS / SSG.</strong> <a href="https://www.smashingmagazine.com/2019/05/switch-wordpress-hugo/">Switching from WordPress to Hugo</a>, a guide by Christopher Kirk-Nielsen / <a href="https://blog.callr.tech/static-web-roots/">Static web — back to the roots?</a> and <a href="https://blog.callr.tech/static-website-performance-seo/">The edge of the static web</a>, two articles by Brice Berdah</p>
<p><strong>Explorable explanations.</strong> <a href="https://www.meltingasphalt.com/interactive/going-critical/">Going Critical</a> by Kevin Simler</p>
<p><strong>React / Web Components.</strong> <a href="https://medium.com/seek-blog/a-unified-styling-language-d0c208de2660">A Unified Styling Language</a> by Mark Dalgleish / watch this space: <a href="https://github.com/achou11/no-virtual-dom">no-virtual-dom</a> is a short list of libraries and frameworks that don't use the Virtual DOM approach to managing UI state</p>
<p><strong>UX / Accessibility.</strong> <a href="https://a11yproject.com/checklist/">Accessibility Checklist</a> from the A11Y Project / <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">Accessible Icon Buttons</a> by Sara Soueidan / <a href="https://medium.com/@mandy.michael/building-websites-for-safari-reader-mode-and-other-reading-apps-1562913c86c9">Building websites for Reader Mode</a> by Mandy Michael / <a href="https://tink.uk/the-difference-between-keyboard-and-screen-reader-navigation/">The difference</a> between keyboard and screen reader navigation, by Léonie Watson</p>
<p><strong>Deep dives.</strong> <a href="http://westonthayer.com/writing/intro-to-font-metrics/">Intro to font metrics</a> by Weston Thayer</p>
<p><strong>Software Architecture.</strong> Martin Fowler on <a href="https://martinfowler.com/bliki/TechnicalDebt.html">Technical Debt</a></p>
<p><strong>Toolbox.</strong> Skia, the graphics engine that powers Firefox and Chrome, has a suprisingly nice documentation; they've made some <a href="https://skia.org/user/modules/pathkit">path geometry utilities</a> available in JavaScript (via WASM) / Keyframe-based responsive design with <a href="https://typetura.scottkellum.com/typetura-js">Typetura.js</a></p>
<p><strong>Work(life).</strong> <a href="http://www.zeldman.com/2019/05/22/you-got-this/">You got this.</a> by Jeffrey Zeldman / <a href="https://rain-1.github.io/why/why.html">Why did I write my programming projects?</a></p>
<hr />
<p>Thoughts I came across this week:</p>
<blockquote>
<p>Technology won't save us (sorry, AV's). Innovations are not required. Solutions we seek for the future can be found in principles of the past. (<a href="https://twitter.com/theAoU/status/1131586700822622218">source</a>)</p>
</blockquote>
<p>and</p>
<blockquote>
<p>The first step in solving any problem is to dramatically underestimate its difficulty. (<a href="https://twitter.com/sebastiangood/status/1131169564660776960">source</a>)</p>
</blockquote>
<hr />
<p><em>Soundtrack: Kangding Ray and Zomby (on repeat)</em></p>
wsf-ix2019-05-20T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-05-20/<p><strong>Web Platform.</strong> <a href="https://github.com/lukeed/dimport">dimport</a>, run ES Module syntax (<code>import</code>, <code>import()</code>, and <code>export</code>) in any browser (even IE) / A canary version of the <a href="https://www.microsoftedgeinsider.com/en-us/download">Chromium-based Edge</a> is now available for macOS</p>
<p><strong>Specs.</strong> <a href="http://kba.cloud/hocr-spec/1.2/">hOCR</a> / <a href="https://github.com/kba/hocrjs">hocrjs</a> / <a href="https://github.com/not-implemented/hocr-proofreader">hocr-proofreader</a></p>
<p><strong>CMS / SSG / WYSIWYG.</strong> A few interesting work-in-progress ideas in this space: <a href="https://github.com/blocks/blocks">blocks</a>, a MDX-based WYSIWYG editor; <a href="https://github.com/kahlil/grit">grit</a>, a Markdown editor for statically generated blogs</p>
<p><strong>React / Web Components.</strong> <a href="https://blog.scottlogic.com/2019/05/09/by-the-hook-a-practical-introduction-to-react-hooks.html">By the Hook</a>, a practical introduction to React hooks by Matt Stobbs / <a href="https://blog.ionicframework.com/announcing-stencil-one-beta/">Announcing Stencil One: Beta</a> by Manu Almeida / <a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead">Virtual DOM is Pure Overhead</a> by Rich Harris</p>
<p><strong>Toolbox.</strong> <a href="https://github.com/modernserf/zebu">zebu</a> by Justin Falcone, a compiler for little languages in tagged template strings / <a href="https://github.com/sindresorhus/env-paths">env-paths</a> by Sindre Sorhus, useful for building CLI apps / <a href="https://tornis.robbowen.digital/">Tornis</a>, a minimal JavaScript library that watches the state of your browser's viewport</p>
<p><strong>Creative coding.</strong> <a href="https://github.com/microsoft/PhoneticMatching">PhoneticMatching</a>, text utilities to do string comparisons on phonemes (the sound of the string), as opposed to characters / <a href="https://github.com/lo-th/Oimo.js">Oimo.js</a>, lightweight 3D physics engine / <a href="https://github.com/BabylonJS/Babylon.js">Babylon.js</a>, a complete JavaScript framework for building 3D games with HTML5 and WebGL / <a href="https://github.com/aaronpenne/generative_art">generative art</a> (with code) by Aaron Penne</p>
<p><strong>Explorable explanations.</strong> <a href="http://www.deeplearning.ai/ai-notes/initialization/">Initializing neural networks</a> / <a href="https://www.joshwcomeau.com/posts/folding-the-dom/">Folding the DOM</a> by Josh Comeau / Yining Shi on <a href="https://blog.schoolofma.org/post/184686755427/yining-shi-on-approaching-technology-creatively">Approaching Technology Creatively, Accessible Tech and Educational Tools</a></p>
<p><strong>Knowledge work.</strong> <a href="https://andymatuschak.org/books/">Why books don't work</a> by Andy Matuschak / <a href="https://matthiasott.com/articles/into-the-personal-website-verse">Into the personal website-verse</a> by Mattias Ott / <a href="http://brendandawes.com/blog/permission-to-write-anything">Permission to write stuff</a> by Brendan Dawes</p>
<hr />
<p><strong>TIL.</strong> I had heard the notion of <strong>zero-cost abstraction</strong> before, but <a href="https://boats.gitlab.io/blog/post/zero-cost-abstractions/">this article</a> clarified it for me / <a href="https://joshpeterson.github.io/a-zero-cost-abstraction">This one</a> is also good</p>
<hr />
<p><em>Soundtrack: Kangding Ray — Predawn Qualia</em></p>
wsf-viii2019-05-09T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-05-09/<p><strong>Web platform.</strong> <a href="https://www.bleepingcomputer.com/news/software/mozilla-firefox-to-enable-hyperlink-ping-tracking-by-default/">Firefox to Enable Hyperlink Ping Tracking By Default</a>, and a TIL for me that the <code>ping</code> attribute exists and it's enabled already in the other browsers / <a href="https://www.youtube.com/watch?v=dAIckpwW9ds">Making Future Interfaces: ES Modules</a> (video) by Heydon Pickering is extremely funny / <a href="https://tobiasahlin.com/blog/masonry-with-css/">CSS Masonry</a> with <code>flexbox</code>, <code>:nth-child()</code>, and <code>order</code>, by Tobias Ahlin</p>
<p><strong>Creative coding.</strong> <a href="https://tinkersynth.com/">Tinkersynth</a> by Josh Comeau is now <a href="https://github.com/joshwcomeau/tinkersynth">open source</a> / I've just seen the movie <em>Arrival</em> (2016) and apparently Stephen Wolfram <a href="https://blog.stephenwolfram.com/2016/11/quick-how-might-the-alien-spacecraft-work/">did some work for it</a> / <a href="https://ptsjs.org/">Pts.</a>, a JavaScript creative coding library / Sam Saccone has written <a href="https://github.com/samccone/thermal_print">a JS driver for thermal printers</a>, Susie Lu has done <a href="https://twitter.com/DataToViz/status/1124752405973782528">some receipt dataviz</a> with it</p>
<p><strong>React / Web Components.</strong> web.dev's publishing <a href="https://web.dev/react">guides for performant React apps</a> / <a href="https://developers.facebook.com/videos/2019/intro-to-react-hooks/">Intro to React hooks</a> (video) by Kathryn Middleton / <a href="https://milesj.gitbook.io/interweave/">interweave</a>, a React library to safely render HTML without <code>dangerouslySetInnerHTML</code> / <a href="https://github.com/phtmlorg/phtml">phtml</a>: transform HTML with JavaScript (<a href="https://codepen.io/jonneal/post/why-is-there-phtml">why</a>)</p>
<p><strong>UX / Accessibility.</strong> Read Erin Kissane's classic book, <a href="http://elements-of-content-strategy.abookapart.com/"><em>The Elements of Content Strategy</em></a>, online / <a href="https://www.inkandswitch.com/local-first.html">Local-first software</a> (You own your data, in spite of the cloud), latest essay by the always excellent <a href="https://www.inkandswitch.com/">Ink & Switch</a> / <a href="https://www.flotato.com/">Flotato</a> / WPCampus have requested an accessibility audit for Gutenberg (WordPress's new post editor); the <a href="https://wpcampus.org/2019/05/gutenberg-audit-results/">results are in</a>, and Rachel Cherry has gone through all of it and <a href="https://twitter.com/bamadesigner/status/1124719434852175873">posted nuggets</a> in a Twitter megathread / Microsoft's <a href="https://www.microsoft.com/en-us/research/publication/guidelines-for-human-ai-interaction/">Guidelines for Human-AI Interaction</a> / GOV.UK's <a href="https://www.gov.uk/service-manual">Service Manual</a> / <a href="https://www.ianbicking.org/blog/2019/03/firefox-experiments-i-would-have-liked.html">The Firefox experiments I would have liked to try</a> by Ian Bicking</p>
<p><strong>Explorables.</strong> <a href="https://parametric.press/issue-01/unraveling-the-jpeg/">Unraveling the JPEG</a> by Omar Shehata, from the first issue of <a href="https://parametric.press/">Parametric Press</a> / <a href="http://immersivemath.com/">Immersive Math</a> by Jacob Ström, Kalle Åström, and Tomas Akenine-Möller</p>
<p><strong>Keeping up.</strong> <a href="https://speakerdeck.com/simon/instant-serverless-apis-powered-by-sqlite">Instant serverless APIs, powered by SQLite</a> (slides) by Simon Willison, with some references in <a href="https://twitter.com/simonw/status/1124750304522715136">the thread</a></p>
<p><strong>Knowledge work.</strong> <a href="https://www.mdpi.com/2079-8954/7/2/25">Making Sense of Complexity: Using SenseMaker as a Research Tool</a> (paper) / <a href="https://webprojectbook.com/">The Web Project Guide</a>, From Spark to Launch and Beyond</p>
<p><strong>Work(life).</strong> <a href="https://interactions.acm.org/archive/view/may-june-2019/impostor-syndrome-and-burnout">Impostor Syndrome and Burnout</a>, some reflections by Elizabeth Churchill / <a href="https://blog.testdouble.com/posts/2019-05-08-the-selfish-programmer">The Selfish Programmer</a>, video + transcript by Justin Searls / <a href="https://lobste.rs/s/bazqzk/nih_bingo_wheels_you_constantly_want">I asked Lobste.rs</a>: (paraphrasing) <em>What are some software project ideas that have been low-key bugging you for a while, and despite usable variants of the idea already out there, you're compelled to write your own?</em>. It got lots of fun answers.</p>
<p><strong>Huh.</strong> <a href="https://en.m.wikipedia.org/wiki/Highly_composite_number">Highly composite numbers</a> are eminently splittable (via Michael Fogleman)</p>
<hr />
<h3>TIL</h3>
<h4>Computed styles in the clipboard</h4>
<p>I was abruptly reminded that when you copy some text from a web page, WebKit-based browsers (Chrome, Safari) will adorn the <code>text/html</code> entry in the clipboard with several inline styles — <code>font-size</code>, <code>font-family</code>, <code>color</code>, <code>background-color</code>, and many more — that it computes by looking at the element and its ancestors in the DOM.</p>
<p>My colleagues and I are working rich text editing capabilities based on <code>contenteditable</code> and when the user pastes some content, you usually want to strip out the extraneous styles. Nobody wants to use a text they copied from a "dark mode" website <em>as-is</em>, right?</p>
<p>But now we wanted to add support for coloring and highlighting portions of the text in the RTE editor, and realized that as the user copies and pastes text around, we can't discern whether the values for <code>color</code> and <code>background-color</code> are something our editor has set (matching the author's intention) or whether it was picked up from some ancestor in the DOM (and should therefore be discarded).</p>
<p>After many attempts at <em>somehow</em> marking up the <code><span></code>s so that we know <em>we had set the color</em>, and failing due to the browser's style computation process wherein all CSS colors are serialized to their <code>rgba()</code> equivalent, we found a variant that seems promising:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">contenteditable</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--dummy-var<span class="token punctuation">,</span> #f0f0f0<span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>Hello<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><br /> World<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Using an inexistent CSS variable with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#Custom_property_fallback_values">a fallback value</a> we prevent Chrome/Safari from computing a <code>rgba()</code> equivalent of the color and we can definitely tell that it was us who put it there. More details <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=960315">in this Chromium issue</a>.</p>
<p>P.S.: You can use <a href="https://evercoder.github.io/clipboard-inspector/"><code>clipboard-inspector</code></a> to peek into your clipboard.</p>
<h4>Loading local files in <a href="https://observablehq.com/">Observable</a></h4>
<p>As I'm adding features to <a href="https://github.com/evercoder/culori">culori</a>, Observable is the perfect place to test them visually before I push a new version to npm. I wondered whether you could load a local version of the library into a notebook, so I don't have to publish pre-releases to npm (which, to this day, I'm not 100% sure how to do), and it turns out you can!</p>
<p>You just start up a local server (the quickest is to run <code>python -m SimpleHTTPServer</code> in your project directory), and then in the notebook load the library from <code>127.0.0.1</code>:</p>
<pre class="language-js"><code class="language-js">culori <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http://127.0.0.1:8000/build/culori.umd.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<blockquote>
<p>In Firefox, using <code>localhost</code> instead of <code>127.0.0.1</code> <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=903966">won't work</a>.</p>
</blockquote>
<p>Mike Bostock points out that you can also <a href="https://observablehq.com/@mbostock/reading-local-files">use <code><input type='file'></code></a> in the notebook to load local files.</p>
<hr />
<p>Claude Shannon was born 103 years ago. Here are <a href="https://medium.com/@jimmysoni/on-claude-shannons-103rd-birthday-here-are-103-memorable-claude-shannon-quotes-maxims-and-843de4c716cf">103 of his quotes</a> compiled by Jimmy Soni, one of the authors of Shannon's biography <em>A Mind at Play</em>.</p>
<h4>Parting thought</h4>
<blockquote>
<p>Don’t be afraid to start over. This time you’re not starting from scratch, you’re starting from experience. — <a href="https://twitter.com/1996Biggs/status/1124777559281913856">Biggs Burke</a></p>
</blockquote>
wsf-vii2019-04-30T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-04-30/<p><strong>Web platform.</strong> <a href="https://developers.google.com/web/updates/2019/04/nic74">Chrome 74</a> comes with private class fields, the <code>prefers-reduced-motion</code> media query, CSS transition events</p>
<p><strong>Usability / Accessibility.</strong> Scott O'Hara's gallery of <a href="https://scottaohara.github.io/a11y_styled_form_controls/">accessible form controls</a> / <a href="https://twitter.com/dsemumi/status/1121226340085243904">This video</a> of a chimpanzee browsing Instagram has been shared a gazillion times (for good reason) / Ryan Florence is building accessible React components as Reach UI; freshly released, <a href="https://ui.reach.tech/tooltip/">the Tooltip component</a></p>
<p><strong>UI.</strong> Svelte 3 <a href="https://svelte.dev/blog/svelte-3-rethinking-reactivity">has been released</a> / <a href="http://krasimirtsonev.com/blog/article/managing-state-in-javascript-with-state-machines-stent">Are you managing state? Think twice.</a> by Krasimir Tsonev / <a href="https://www.reddit.com/r/reactjs/comments/6al7h2/facebook_has_30000_react_components_how_do_you/dhgruqh/">Facebook has 30,000 React components</a> / <a href="https://vislyhq.github.io/stretch/">Stretch</a> is a flexbox layout engine implemented in Rust that works on the web, iOS, and Android</p>
<p><strong>CMS.</strong> <a href="https://doriantaylor.com/content-management-meta-system">Content Management Meta-System</a> by Dorian Taylor / <a href="https://docs.racket-lang.org/pollen/index.html">Pollen</a> by Matthew Butterick is a SSG written in <a href="https://racket-lang.org/">Racket</a>; its documentation is a pleasure to read</p>
<p><strong>Task runners.</strong> <a href="https://github.com/joshwcomeau/guppy">Guppy</a> by Josh Comeau / <a href="https://github.com/kitze/JSUI">JSUI</a> by Kitze / <a href="https://github.com/alex-saunders/glicky">Glicky</a> by Alex Saunders</p>
<p><strong>Keeping up.</strong> Elixir, Phoenix, Absinthe, GraphQL, React, and Apollo: <a href="https://schneider.dev/blog/elixir-phoenix-absinthe-graphql-react-apollo-absurdly-deep-dive/">an absurdly deep dive</a> by Zach Schneider, and a comically long string of technologies / <a href="https://cybernetist.com/2019/04/25/wasm-universal-application-runtime/">WASM: Universal Application Runtime</a></p>
<p><strong>Creative coding.</strong> <a href="https://github.com/liasomething/shapeConverter">shapeConverter</a> converts Illustrator shapes (saved as Illustrator 8 files) into Processing code / <a href="http://geomalgorithms.com/index.html">Geometry Algorithms</a> by Dan Sunday / <a href="https://github.com/Sinova/Collisions">Collisions</a> by Samuel Hodge, collision detection for circles, polygons, and points</p>
<p><strong>Knowledge work.</strong> <a href="https://github.com/WorldBrain/Memex">WorldBrain/Memex</a> is an open-source browser extension to full-text search your browsing history & bookmarks (!) / <a href="https://www.tunetheweb.com/blog/writing-a-technical-book-for-manning/">Writing a technical book for Manning</a> by Barry Pollard</p>
<hr />
<h3>Today I Learned</h3>
<p><strong>Tagged template literals.</strong> Andy Bell shares <a href="https://gist.github.com/andybelldesign/102804f890f1d8aab6a4325ac141da87">this neat trick</a> for enabling HTML syntax highlighting for plain strings:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">html</span> <span class="token operator">=</span> <span class="token parameter">str</span> <span class="token operator">=></span> str<span class="token punctuation">;</span><br /><br /><span class="token comment">// without the tag</span><br /><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><div class='hello'><br /> World<br /></div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><br /><span class="token comment">// with the tag</span><br />html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><div class="hello">World</div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<p><code>String.raw</code> (<a href="https://danburzo.ro/watchstarfork/2019-04-18/">last edition's TIL</a>) also works nicely for this purpose, and allows interpolating values in the template:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> html <span class="token operator">=</span> String<span class="token punctuation">.</span>raw<span class="token punctuation">;</span><br /><br />html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><div class=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>className<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<p><em>Update:</em> There are some differences between <code>String.raw</code> and having no tag, so use <a href="https://npmjs.com/package/dummy-tag"><code>dummy-tag</code></a> instead for the most consistent result.</p>
<p><strong>Webpack performance.</strong> To find out which steps in a Webpack build process take most time, run your build with these flags:</p>
<pre class="language-bash"><code class="language-bash">webpack <span class="token parameter variable">--profile</span> <span class="token parameter variable">--progress</span></code></pre>
<p>You'll get a list of timings like the one below (which is abridged for brevity):</p>
<pre><code>12527ms building modules
40ms hashing
491ms chunk assets processing
19280ms chunk asset optimization
3566ms after chunk asset optimization
215ms emitting
</code></pre>
<hr />
<p><em>Soundtrack: Jay-Jay Johanson — Kings Cross</em></p>
wsf-vi2019-04-18T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-04-18/<p><strong>Web platform.</strong> <a href="https://www.filamentgroup.com/lab/html-includes/">HTML includes that work today</a> by Scott Jehl / <a href="https://github.com/alterebro/accessible-image-lazy-load">accessible-image-lazy-load</a>, an image loading technique using <code><a></code> elements / <a href="https://www.dannymoerkerke.com/blog/web-components-will-replace-your-frontend-framework">Web Components will replace your front-end framework</a> by Danny Moerkerke; they probably won't, but a nice intro to Web Components nonetheless.</p>
<p><strong>Reference.</strong> <a href="https://github.com/git-tips/tips">Git tips</a>, recipes for common Git tasks / <a href="http://jeffe.cs.illinois.edu/teaching/algorithms/">Algorithms</a>, a CC-licensed book by Jeff Erickson</p>
<p><strong>Open-source.</strong> <a href="https://formidable.com/blog/2019/oss-maintenance/">The levels of OSS maitenance</a>, a handy chart</p>
<p><strong>Usability / Accessibility.</strong> <a href="https://www.nngroup.com/articles/modes/">Modes in User Interfaces</a> by Page Laubheimer (Nielsen Norman Group) / <a href="http://adrianroselli.com/2019/03/under-engineered-toggles.html">Under-engineered Toggles</a> by Adrian Roselli</p>
<p><strong>Work(life).</strong> Stefanie Posavec <a href="https://twitter.com/stefpos/status/1118070093303435265">asks</a>: <em>any tips for how you've actively gotten out of creative ruts / changed the direction of your creative practice?</em> / <a href="https://www.subtraction.com/2016/08/11/the-underestimated-merits-of-copying-someone-elses-work/">The Underestimated Merits of Copying Someone's Work</a> (2016) by Khoi Vinh.</p>
<p><strong>CMS.</strong> <a href="https://github.com/brandonweiss/charge">charge</a>, a static site generator. (cf. <em>The underestimated merits of implementing your own SSG</em>) / <a href="https://github.com/lukejacksonn/servor">servor</a>, a very lightweight local server.</p>
<p><strong>TIL.</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw"><code>String.raw</code></a> is the "default" tag function, (almost¹) the same as writing a template literal without a tag:</p>
<pre class="language-js"><code class="language-js">String<span class="token punctuation">.</span>raw<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">You have </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token number">10</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> items</span><span class="token template-punctuation string">`</span></span> <span class="token comment">// => "You have 10 items"</span><br /><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">You have </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token number">10</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> items</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token comment">// => "You have 10 items"</span></code></pre>
<p>¹ <strong>Update:</strong> There are some difference between the two in regards to backslash escaping:</p>
<pre class="language-js"><code class="language-js">String<span class="token punctuation">.</span>raw<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello\nWorld</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token comment">// => "Hello\\nWorld"</span><br /><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello\nWorld</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token comment">// => "Hello\nWorld"</span></code></pre>
<p>You can, instead, use the <a href="https://npmjs.com/package/dummy-tag"><code>dummy-tag</code></a> package, which implements a default interpolation.</p>
<hr />
<blockquote>
<p>The value of a prototype is in the education it gives you, not in the code itself. — Alan Cooper</p>
</blockquote>
<p><em>Soundtrack: <a href="https://www.youtube.com/watch?v=PznUsKnRuSU">Nadine Shah — Stealing Cars</a></em></p>
wsf-v2019-04-08T00:00:00Zhttps://danburzo.ro/watchstarfork/2019-04-08/<p><strong>CSS.</strong> <a href="https://www.theguardian.com/info/2019/apr/04/revisiting-the-rendering-tier">Revisiting the rendering tier</a> / <a href="http://piccalil.li/">Piccalilli</a>, a newsletter by Andy Bell / <a href="http://clagnut.com/blog/2395">Hyphenation in CSS</a>.</p>
<p><strong>CMS.</strong> <a href="https://postlight.com/trackchanges/introducing-liftoff-easily-create-a-static-website-powered-by-airtable">SSG powered by Airtable</a>, why not.</p>
<p><strong>Build tools (Or lack thereof).</strong> <a href="https://benfrain.com/moving-from-gulp-to-parcel/">Moving from Gulp to Parcel</a> / <a href="https://formidable.com/blog/2019/no-build-step/">Don't build that app!</a> / <a href="https://www.pikapkg.com/blog/pika-web-a-future-without-webpack">A future without Webpack</a></p>
<p><strong>Web platform.</strong> Native lazy-loading: images (and iframes) will get <a href="https://addyosmani.com/blog/lazy-loading/">a <code>loading</code> attribute</a>! / Chrome 73 brings PWAs to the Mac (<a href="https://developers.google.com/web/updates/2019/03/nic73">release notes</a>) — oh, and all scroll listeners are <a href="https://www.chromestatus.com/features/6662647093133312">passive by default now</a> / Safari 12.1 for iOS comes with support for <code><input type='color'/></code>, media queries for Dark Mode, Web Share API, and more (<a href="https://webkit.org/blog/8718/new-webkit-features-in-safari-12-1/">release notes</a>) / Pointer Events Level 2 is a <a href="https://www.w3.org/TR/2019/REC-pointerevents2-20190404/">W3C Recommendation</a> / <a href="https://htmlparser.info/">Idiosyncracies of the HTML parser</a></p>
<p><strong>Web Components / Accessibility.</strong> <a href="https://open-wc.org/">Open Web Component Recommendations</a> / <a href="https://github.com/webcomponents/gold-standard/wiki">The Gold Standard Checklist</a> / <a href="https://www.w3.org/2001/tag/doc/webcomponents-design-guidelines/">Guidelines</a> from the W3C TAG / Tom MacWright <a href="https://macwright.org/2019/04/06/accessibility-buttons.html">on <code><button></code> elements</a> / Scott O'Hara's <a href="https://www.scottohara.me/note/2019/04/03/switch-script.html">Updated Switch script</a> / Elizabeth Schafer on <a href="https://dev.to/elizabethschafer/designing-button-focus-states-for-better-usability-gm2">Designing button focus states</a> for better usability</p>
<p><strong>React.</strong> <a href="https://www.youtube.com/watch?v=G-aO5hzo1aw">Trying React Hooks for the first time</a> (video)</p>
<p><strong>Tutorials.</strong> <a href="https://demoman.net/">Demo-Man</a> interactive tutorials explaining useful math for game development, but also of interest to creative coders / <a href="https://www.redblobgames.com/making-of/little-things/">Little Things</a> by Amit Patel</p>
<p><strong>Creative coding.</strong> Recent experiments from <a href="https://twitter.com/inconvergent/status/1110213502634000385">Anders Hoff</a> and <a href="https://twitter.com/mattdesl/status/1110695190413623296">Matt DesLauriers</a> / <a href="https://generative.fm/">Generative.fm</a> by Alex Bainter (<a href="https://github.com/generative-music/generative.fm">repo</a>), <a href="https://medium.com/@metalex9/making-generative-music-in-the-browser-bfb552a26b0b">Making Generative Music in the Browser</a> / See also: <a href="https://teropa.info/loop">How Generative Music Works</a> by Tero Parviainen</p>
<p><strong>Data viz.</strong> Sarah Leo, <a href="https://medium.economist.com/mistakes-weve-drawn-a-few-8cdd8a42d368">Mistakes, we've drawn a few</a> / <a href="https://datavizhandbook.info/">Data Visualization Handbook</a>; the book is gorgeous, and the site contains a few useful PDF freebies.</p>
<p><strong>Work(life)</strong> <a href="https://tomcritchlow.com/2019/04/04/the-strategic-independent/">The Strategic Independent</a> by Tom Critchlow.</p>
<p><strong>Typefaces.</strong> <a href="https://public-sans.digital.gov/">Public Sans</a> by the US Web Design System.</p>
<p><strong>Tip.</strong> <a href="https://twitter.com/danburzo/status/1111169526786342912">Did you know</a> you can long-press the new tab button in iOS Safari to get the option to <em>close all tabs</em>? Instant life improvement.</p>
<p><em>Soundtrack: Beth Gibbons & The Polish National Radio Symphony Orchestra play Henryk Górecki's Symphony no. 3</em></p>
wsf-iv2018-07-14T00:00:00Zhttps://danburzo.ro/watchstarfork/2018-07-14/<p><strong>React.</strong> Mapping it out — <a href="https://github.com/adam-golab/react-developer-roadmap">Developer roadmap</a> and <a href="https://ifelse.io/2018/07/04/a-guide-to-the-react-ecosystem/">A guide to the ecosystem</a> / You really <em>can</em> get started with React in a few minutes: <a href="https://medium.com/yld-engineering-blog/react-is-just-javascript-88600553269c">React is just JavaScript</a> and <a href="http://claytonsalem.com/easy-peasy-react-cheesy/">Easy Peasy React Cheesy</a> / Still on the topic of learning, Brandon Dail <a href="https://twitter.com/aweary/status/1015277495690539009">says</a>:</p>
<blockquote>
<p>If you're looking to step up your @reactjs game, try reading through the test suite. They use the public APIs so they're easy to read if you're familiar with the basics, and they cover almost all supported use cases!</p>
</blockquote>
<p><strong>Creative coding.</strong> Raven Kwok's <a href="http://ravenkwok.com/autotroph/">Autotroph</a> page includes a great breakdown video on how it's built / In case you missed it: <a href="https://twitter.com/alcor/status/1014536840638955526">bitty.site</a>, which lets you host small web pages in the URL itself, has been doing the rounds.</p>
<p><strong>Learning.</strong> <a href="https://internetingishard.com/">Interneting is hard</a>, friendly web development tutorials for complete beginners / How to make CSS Grid work everywhere (*cough* IE), a three-part series: <a href="https://css-tricks.com/css-grid-in-ie-debunking-common-ie-grid-misconceptions/">1st</a>, <a href="https://css-tricks.com/css-grid-in-ie-css-grid-and-the-new-autoprefixer/">2nd</a>, and <a href="https://css-tricks.com/css-grid-in-ie-faking-an-auto-placement-grid-with-gaps/">3rd</a> / Regular expressions are great no matter what anybody tells you; here's Surma's <a href="https://dassur.ma/things/regexp-quote/">most useful RegExp trick</a> / I'd given up trying to keep up with Chrome's dev tools, but this is helpful: <a href="https://www.sitepoint.com/optimization-auditing-a-deep-dive-into-chromes-dev-console/">A Deep Dive into Chrome’s Dev Console</a> / <a href="https://www.smashingmagazine.com/2018/07/web-with-just-a-keyboard/">I Used The Web For A Day With Just A Keyboard</a> / <a href="https://brutalist-web.design/">Guidelines for Brutalist Web Design</a>.</p>
<p><strong>Essays.</strong> <a href="https://austinkleon.com/2018/07/05/you-dont-have-to-live-in-public/">You don't have to live in public</a> / <a href="https://kottke.org/18/07/did-blogs-ruin-the-web-or-did-the-web-ruin-blogs">Did blogs ruin the web? Or did the web ruin blogs?</a> / <a href="http://metalbat.com/feel-good/">Your App Is Good And You Should Feel Good</a></p>
<p><strong>Parting thought.</strong> <a href="https://twitter.com/Folletto/status/1014815194244157440">The cycle of life</a>:</p>
<blockquote>
<p>We design a simple system.
Then we make it more complicated.
More complicated.
More complicated.
Then someone else makes a new simple system.
People switch to that one.
Then they make it more complicated.........</p>
</blockquote>
<p><em>Soundtrack:</em> Opa Tsupa — Trois francs six sous</p>
wsf-iii2018-07-07T00:00:00Zhttps://danburzo.ro/watchstarfork/2018-07-07/<blockquote>
<p>All I want is to be someone that makes new things and thinks about them. — a haiku by John Maeda</p>
</blockquote>
<p><strong>React.</strong> <a href="https://github.com/atlassian/react-beautiful-dnd">react-beautiful-dnd</a> has just hit version 8, and seems a robust and comprehensive library for drag-and-drop interactions / <a href="https://github.com/soska/react-keyboardist">react-keyboardist</a> is for adding keyboard shortcuts to your app / <a href="https://github.com/kettanaito/atomic-layout">atomic-layout</a> uses CSS Grid to build layouts, looks promising / <a href="https://github.com/kay-is/react-from-zero">react-from-zero</a> uses annotated code examples that work directly in the browser to teach React basics.</p>
<p><strong>Essays.</strong> <a href="https://css-tricks.com/balancing-time/">Balancing Time</a> / <a href="https://chriscoyier.net/2013/10/18/mediocre-ideas-showing-up-and-persistence/">Mediocre ideas, showing up, and persistence</a> / <a href="https://ar.al/2018/06/29/reclaiming-rss/">Reclaiming RSS</a> / <a href="http://tom.preston-werner.com/2010/08/23/readme-driven-development.html">Readme Driven Development</a>, and <a href="https://www.allthingsdistributed.com/2006/11/working_backwards.html">Working Backwards</a> / <a href="http://randsinrepose.com/archives/anti-flow/">Anti-Flow</a>.</p>
<p><strong>Reference.</strong> <a href="https://github.com/stereobooster/package.json">An explanation</a> of all the different fields in <code>package.json</code> / <a href="https://inclusivedesignprinciples.org/">Inclusive design principles</a> / <a href="https://medium.com/@jesseddy/tips-for-designers-to-become-better-copywriters-from-the-experts-part-1-cbd3720cbd88">Tips for writing better</a>.</p>
<p><strong>Parting thought.</strong> Programming wisdom around "premature" optimization — identifying it as the root of all evil, even — is one of the maddeningly pervasive sound bites that tends to shore up on my timeline from time to time. Left unexamined, it's misunderstood as a carte blanche for writing inefficient code. Emil Persson <a href="http://www.humus.name/index.php?page=News&ID=383">writes a response</a> to the latest offender.</p>
<p><em>Soundtrack:</em> Ian William Craig — A Turn of Breath.</p>
wsf-ii2018-06-24T00:00:00Zhttps://danburzo.ro/watchstarfork/2018-06-24/<p><strong>React.</strong> <a href="https://github.com/ctrlplusb/react-sizeme">react-sizeme</a> sounds like it could be useful for measuring DOM nodes after they're rendered / The React folks are working on a <a href="https://twitter.com/brian_d_vaughn/status/1009977215176491008">Profiler</a> tool, which may make performance enhancements easier than with packages such as <em>why-did-you-update</em> / <a href="https://github.com/diegomura/react-pdf">react-pdf</a> promises to let you build PDFs in JSX.</p>
<p><strong>Design Systems.</strong> The GDS system published the <a href="https://design-system.service.gov.uk/">GOV.UK Design System</a>.</p>
<p><strong>Static site generators.</strong> After I stared working on <a href="https://github.com/marceljs">marcel</a>, I learned about <a href="https://github.com/11ty/eleventy">eleventy</a>, which looks <em>great</em> and shares the ethos, I think / <a href="https://github.com/twigjs/twig.js">twig.js</a> and <a href="https://github.com/ericmorand/twing">twing</a> are two JavaScript implementations of the Twig template language, which you may remember is my favorite.</p>
<p><strong>Creative coding.</strong> <a href="https://rabbitear.org/">rabbitear</a> is an origami-building library for the web.</p>
<p><strong>TIL.</strong> I didn't know this about this about the String <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split">split()</a> method:</p>
<blockquote>
<p>If <code>separator</code> is a regular expression that contains capturing parentheses, then each time <code>separator</code> is matched, the results (including any undefined results) of the capturing parentheses are spliced into the output array.</p>
</blockquote>
<p><em>Soundtrack.</em> <a href="https://umorrex.bandcamp.com/album/shunter">Driftmachine — Shunter</a></p>
wsf-i2018-06-18T00:00:00Zhttps://danburzo.ro/watchstarfork/2018-06-18/<p><strong>React.</strong> <a href="https://github.com/bvaughn/react-window">react-window</a> looks like a fast way to render <em>lots</em> of objects in a viewport, without compromising the performance; seems to be a refresh of <a href="https://github.com/bvaughn/react-virtualized">react-virtualized</a>? / Speaking of refreshes, <a href="https://github.com/reach/router">reach-router</a> from the creator of <a href="https://github.com/ReactTraining/react-router">react-router</a> / <a href="https://github.com/renatorib/react-powerplug">react-powerplug</a> has some useful little components / <a href="https://github.com/jossmac/react-focus-marshal">react-focus-marshall</a> traps the keyboard tabbing focus within a component / I should check out <a href="https://github.com/transitive-bullshit/create-react-library">create-react-library</a>.</p>
<p><strong>Static site generators.</strong> Regular reminder that <a href="http://gohugo.io/">Hugo</a> is <em>very</em> fast / <a href="https://github.com/hexojs/hexo">Hexo</a> is written in JavaScript and works with nunjucks templates, which are like Twig templates from PHP (or Liquid in Ruby, or Jinja2 in Python) — my favorite templating language.</p>
<p><strong>Bundlers.</strong> <a href="https://github.com/parcel-bundler/parcel">parcel</a> is fast and lovely and you set it up in about a minute for a new project.</p>
<p><strong>Coding life.</strong> Finally looked at <a href="https://prettier.io/">Prettier</a> and like it very much; also added <a href="https://github.com/typicode/husky">husky</a> and set up a <code>precommit</code> hook to run prettier on committed code / <a href="https://github.com/pedronauck/docz">docz</a> is a promising — but still in progress — project on writing docs for React projects using MDX (Markdown + JSX); and Facebook launched <a href="https://docusaurus.io/">docusaurus</a>, which is more in the vein of <a href="http://gatsbyjs.org/">Gatsby</a>, I think.</p>
<hr />
<p><em>Soundtrack.</em> Roger Eno — Dust of Stars</p>
My favorite records from 20232023-12-23T00:00:00Zhttps://danburzo.ro/favorite-records-2023/<p><em>Note:</em> I will be updating this list beyond 2023. The additions are marked with the manicule (☞).</p>
<h2>Heavy rotation</h2>
<p>Some of the albums I’ve listened to a lot this year, in alphabetical order.</p>
<ol>
<li>Alternativ Quartet — Deocamdată suntem [<a href="https://alternativquartet.bandcamp.com/album/deocamdat-suntem">s/r</a>]</li>
<li>Alternativ Quartet — Departe de solstițiu [<a href="https://alternativquartet.bandcamp.com/album/departe-de-solsti-iu">s/r</a>]</li>
<li>Arvo Pärt — Tractus [<a href="https://ecmrecords.com/product/arvo-part-tractus-estonian-philharmonic-chamber-choir-tallinn-chamber-orchestra-tonu-kaljuste/"><abbr>ECM</abbr></a>]</li>
<li>Balmorhea — Pendant World [<a href="https://balmorheamusic.com/">Deutsche Grammophon</a>]</li>
<li>Baxter Dury — I Thought I was Better than You [<a href="https://baxterdury.bandcamp.com/album/i-thought-i-was-better-than-you">Heavenly</a>]</li>
<li>Beirut — Hadsel [<a href="https://beirut.lnk.to/Hadsel">Pompeii</a>]</li>
<li>Ben Howard — Is it? [<a href="https://benhowardisit.bandcamp.com/album/is-it">Island</a>]</li>
<li>The Blaze — Jungle [<a href="https://theblaze.bfan.link/jungle-album.web">Animal63</a>]</li>
<li>Blonde Redhead — Sit Down for Dinner [<a href="https://blonderedhead.bandcamp.com/album/sit-down-for-dinner">Section1</a>]</li>
<li>Brian Eno & Fred again.. — Secret Life [<a href="https://lnk.to/TEXT055">Text</a>]</li>
<li>Bruce Brubaker — Eno Piano [<a href="https://bruce-brubaker.bandcamp.com/album/eno-piano">InFiné</a>]</li>
<li>Constant Smiles — Kenneth Anger [<a href="https://constantsmiles.bandcamp.com/album/kenneth-anger">Sacred Bones</a>]</li>
<li>Devendra Banhart — Flying Wig [<a href="https://devendrabanhart.bandcamp.com/album/flying-wig">Mexican Summer</a>]</li>
<li>El Michels Affair & Black Thought — Glorious Game [<a href="https://elmichelsaffair.bandcamp.com/album/glorious-game">Big Crown</a>]</li>
<li>Eluvium — (Whirring Marvels In) Consensus Reality [<a href="https://eluvium.bandcamp.com/album/whirring-marvels-in-consensus-reality">Temporary Residence</a>]</li>
<li>Erland Cooper & Scottish Ensemble — Folded Landscapes [<a href="https://erlandcooper.lnk.to/FoldedLandscapes">Mercury KX</a>]</li>
<li>Fever Ray — Radical Romantics [<a href="https://feverray.bandcamp.com/album/radical-romantics">Rabid</a>]</li>
<li>Gregory Alan Isakov — Appaloosa Bones [<a href="https://gregoryalanisakov.bandcamp.com/album/appaloosa-bones">s/r</a>]</li>
<li>☞ Iuliad — Mă așteptam la alt sfârșit [s/r]</li>
<li>James Blake — Playing Robots Into Heaven [<a href="https://jamesblake.lnk.to/PRIH">Republic/Polydor</a>]</li>
<li>☞ James Holden — Imagine This Is A High Dimensional Space Of All Possibilities [<a href="https://jamesholden.bandcamp.com/album/imagine-this-is-a-high-dimensional-space-of-all-possibilities">Border Community</a>]</li>
<li>Jay-Jay Johanson — Fetish [ART/29 Music]</li>
<li>Jonathan Bree — Pre-Code Hollywood [<a href="https://jonathanbree.bandcamp.com/album/pre-code-hollywood">Lil’ Chief</a>]</li>
<li>Kelela — Raven [<a href="https://kelela.bandcamp.com/album/raven">Warp</a>]</li>
<li>King Creosote — <span class="sc">I DES</span> [<a href="https://kingcreosote.bandcamp.com/album/i-des">Domino</a>]</li>
<li>Laurel Halo — Atlas [<a href="https://laurelhalo.bandcamp.com/album/atlas">Awe</a>]</li>
<li>Lubomyr Melnyk — The Sacred Thousand [<a href="https://jersikarecords.bandcamp.com/album/the-sacred-thousand">Jersika</a>]</li>
<li>The National — First Two Pages of Frankenstein [<a href="https://thenational.bandcamp.com/album/first-two-pages-of-frankenstein">4AD</a>]</li>
<li>The National — Laugh Track [<a href="https://thenational.bandcamp.com/album/laugh-track">4AD</a>]</li>
<li>Philip Selway — Strange Dance [<a href="https://philipselway.bandcamp.com/album/strange-dance">Bella Union</a>]</li>
<li>Roger Eno — The Skies, they shift like chords… [<a href="https://store.deutschegrammophon.com/p51-i0028948650217/roger-eno/the-skies-they-shift-like-chords/index.html">Deutsche Grammophon</a>]</li>
<li>Romy — Mid Air [<a href="https://romyromyromy.bandcamp.com/album/mid-air">Young</a>]</li>
<li>Sigur Rós — Átta [<a href="https://sigurros.bandcamp.com/album/tta">Von Dur</a>]</li>
<li>Sofia Kourtesis — Madres [<a href="https://sofiakourtesis.bandcamp.com/album/madres-2">Ninja Tune</a>]</li>
<li>Yara Asmar — Synth Waltzes and Accordion Laments [<a href="https://yaraasmar.bandcamp.com/album/synth-waltzes-and-accordion-laments">Hive Mind</a>]</li>
</ol>
<p>Quite into Daniel Lanois’s <a href="https://daniellanois.lnk.to/playerpiano">Player, Piano</a> (from <a href="https://danburzo.ro/favorite-records-2022/">2022</a>) and <a href="https://enzofavata.bandcamp.com/album/enzo-favata-the-crossing-2">Enzo Favata The Crossing</a> (from <a href="https://danburzo.ro/favorite-records-2021/">2021</a>) as well, both of which I learned about just this year.</p>
<h2>On the radar</h2>
<p>These ones I enjoyed but didn’t get to dig into as much as I’d hoped.</p>
<ol>
<li>All Hands_Make Light — “Darling The Dawn” [<a href="https://allhandsmakelight.bandcamp.com/album/darling-the-dawn">Constellation</a>]</li>
<li>Altın Gün — Aşk [<a href="https://altingun.bandcamp.com/album/a-k">Glitterbeat</a>]</li>
<li>Alva Noto — HYbr:ID II [<a href="https://noton.info/product/n-061/">Noton</a>]</li>
<li>Alva Noto — Kinder der Sonne [<a href="https://noton.info/product/n-058/">Noton</a>]</li>
<li>Alva Noto — This Stolen Country of Mine [<a href="https://noton.info/product/n-059/">Noton</a>]</li>
<li>Angel Olsen — Forever Means [<a href="https://angelolsen.bandcamp.com/album/forever-means">Jagjaguwar</a>]</li>
<li><span class="sc">ANOHNI</span> — My Back Was a Bridge For You To Cross [<a href="https://anohni.bandcamp.com/album/my-back-was-a-bridge-for-you-to-cross-2">Rough Trade</a>]</li>
<li>Anoushka Shankar — Chapter I: Forever, for Now [<a href="https://anoushkashankar.bandcamp.com/album/chapter-i-forever-for-now">Leiter</a>]</li>
<li>Aphex Twin — Blackbox Life Recorder 21f / In a Room7 F760 [<a href="https://aphextwin.bandcamp.com/album/blackbox-life-recorder-21f-in-a-room7-f760">Warp</a>]</li>
<li>bdrmm — I Don't Know [<a href="https://bdrmm.bandcamp.com/album/i-dont-know">Rock Action</a>]</li>
<li>boygenius — the record [<a href="https://xboygeniusx.bandcamp.com/album/the-record">Interscope</a>]</li>
<li>Carlos Cipa — Ourselves, as we are [<a href="https://carloscipa.bandcamp.com/album/ourselves-as-we-are">Warner Classics</a>]</li>
<li>Chris Abrahams, Oren Ambarchi, Robbie Avenaim — Placelessness [<a href="https://orenambarchi.bandcamp.com/album/placelessness">Ideologic Organ</a>]</li>
<li>Colleen — Le Jour Et La Nuit Du Réel [<a href="https://colleencolleen.bandcamp.com/album/le-jour-et-la-nuit-du-r-el">Thrill Jockey</a>]</li>
<li>Daniel Avery — More Truth [<a href="https://danielavery.bandcamp.com/album/more-truth"><abbr>PIAS</abbr></a>]</li>
<li>David Toop & Lawrence English — The Shell that Speaks the Sea [<a href="https://lawrenceenglish.bandcamp.com/album/the-shell-that-speaks-the-sea">Room40</a>]</li>
<li>Donato Dozzy & Sabla — Crono [<a href="https://gangofducks.bandcamp.com/album/crono">Gang of Ducks</a>]</li>
<li>Eiko Ishibashi & Jim O’Rourke — Lifetime of a Flower [<a href="https://eikoishibashijimorourke.bandcamp.com/album/lifetime-of-a-flower">Week-End</a>]</li>
<li>☞ Explosions in the Sky — End [<a href="https://explosionsinthesky.bandcamp.com/album/end">Temporary Residence</a>]</li>
<li>Föllakzoid — V [<a href="https://follakzoid.bandcamp.com/album/v">Sacred Bones</a>]</li>
<li>Forest Swords — Bolted [<a href="https://forestswords.bandcamp.com/album/bolted">Ninja Tunes</a>]</li>
<li>Greta Van Fleet — Starcatcher [<a href="https://gvf.lnk.to/starcatcher">Republic</a>]</li>
<li>Hania Rani — Ghosts [<a href="https://haniarani.bandcamp.com/album/ghosts">Gondwana</a>]</li>
<li>Hilary Woods — Acts of Light [<a href="https://hilarywoodsmusic.bandcamp.com/album/acts-of-light">Sacred Bones</a>]</li>
<li>James Ellis Ford — The Hum [<a href="https://jamesellisford.bandcamp.com/album/the-hum">Warp</a>]</li>
<li>Jana Horn — The Window Is the Dream [<a href="https://janahorn.bandcamp.com/album/the-window-is-the-dream">No Quarter</a>]</li>
<li>Jim O’Rourke — Hands that Bind OST [<a href="https://jimorourke.bandcamp.com/album/hands-that-bind-original-motion-picture-soundtrack">Drag City</a>]</li>
<li>Julie Byrne — The Greater Wings [<a href="https://juliembyrne.bandcamp.com/album/the-greater-wings">Ghostly</a>]</li>
<li>Julie Byrne — Julie Byrne with Laugh Cry Laugh [<a href="https://juliembyrne.bandcamp.com/album/julie-byrne-with-laugh-cry-laugh">Ghostly</a>]</li>
<li>Kali Malone ft. Stephen O’Malley & Lucy Railton — Does Spring Hide Its Joy [<a href="https://kalimalone.bandcamp.com/album/does-spring-hide-its-joy">Ideologic Organ</a>]</li>
<li>Kevin Morby — More Photographs (A Continuum) [<a href="https://kevinmorby.bandcamp.com/album/more-photographs-a-continuum">Dead Oceans</a>]</li>
<li>Lawrence English & Lea Bertucci — Chthonic [<a href="https://lawrenceenglishleabertucci.bandcamp.com/album/chthonic">American Dreams</a>]</li>
<li>Lawrence English & Werner Dafeldecker — Tropic of Capricorn [<a href="https://hallowground.bandcamp.com/album/lawrence-english-werner-dafeldecker-tropic-of-capricorn">Hallow Ground</a>]</li>
<li>Loscil & Lawrence English — Colours of Air [<a href="https://loscil.bandcamp.com/album/colours-of-air">Kranky</a>]</li>
<li>Luke Schneider — It Is Solved By Walking [<a href="https://lukeschneider.bandcamp.com/album/it-is-solved-by-walking">Third Man</a>]</li>
<li>Natalia Beylis — Mermaids [<a href="https://nataliabeylis.bandcamp.com/album/mermaids">Touch Sensitive</a>]</li>
<li>Noriko Tujiko — Crépuscule I & II [<a href="https://tujikonoriko1.bandcamp.com/album/cr-puscule-i-ii">Editions Mego</a>]</li>
<li>Oneohtrix Point Never — Again [<a href="https://oneohtrixpointnever.bandcamp.com/album/again">Warp</a>]</li>
<li>Oren Ambarchi & Eric Thielemans — Double Consciousness [<a href="https://orenambarchi.bandcamp.com/album/double-consciousness">Matière Mémoire</a>]</li>
<li>Ozmotic & Fennesz — Senzatempo [<a href="https://ozmotic.bandcamp.com/album/senzatempo">Touch</a>]</li>
<li>Philip Jeck & Chris Watson — Oxmardyke [<a href="https://philipjeck.bandcamp.com/album/oxmardyke">Touch</a>]</li>
<li>PJ Harvey — I Inside the Old Year Dying [<a href="https://pjharvey.bandcamp.com/album/i-inside-the-old-year-dying">Partisan</a>]</li>
<li>Rone, Orchestre National de Lyon & Dirk Brossé — L(oo)ping [<a href="https://rone-music.bandcamp.com/album/l-oo-ping">InFiné</a>]</li>
<li>Ryuichi Sakamoto — 12 [Milan/Commmons]</li>
<li>Róisín Murphy — Hit Parade [<a href="https://roisinmurphy.bandcamp.com/album/hit-parade">Ninja Tune</a>]</li>
<li>Sarah Davachi — Long Gradus [<a href="https://sarahdavachi.bandcamp.com/album/long-gradus">Late Music</a>]</li>
<li>Slowdive — everything is alive [<a href="https://slowdive.bandcamp.com/album/everything-is-alive">Dead Oceans</a>]</li>
<li>Sufjan Stevens — Javelin [<a href="https://sufjanstevens.bandcamp.com/album/javelin">Asthmatic Kitty</a>]</li>
<li>Tim Hecker — No Highs [<a href="https://timhecker.bandcamp.com/album/no-highs">Kranky</a>]</li>
<li>Timber Timbre — Lovage [<a href="https://lnk.to/Lovage_Timber_Timbre">Hot Dreams</a>]</li>
<li>Vanessa Wagner — Les heures immobiles [<a href="https://vanessa-wagner.bandcamp.com/album/les-heures-immobiles">InFiné</a>]</li>
<li>Will Butler + Sister Squares — Will Butler + Sister Squares [<a href="https://willbutler.bandcamp.com/album/will-butler-sister-squares">Merge</a>]</li>
<li>Yann Novak — The Voice of Theseus [<a href="https://yannnovak.bandcamp.com/album/the-voice-of-theseus-2">Touch/Fairwood</a>]</li>
<li>Young Fathers — Heavy Heavy [<a href="https://youngfathersofficial.bandcamp.com/album/heavy-heavy">Ninja Tunes</a>]</li>
</ol>
<h2>Other things</h2>
<p>Reissues, expansions, archival material, remixes, live albums, et cetera:</p>
<ol>
<li>Iceland Symphony Orchestra, Daniel Bjarnason — Jóhannsson: A Prayer To The Dynamo and Suites from “Sicario” & “The Theory of Everything” [<a href="https://www.deutschegrammophon.com/en/catalogue/products/a-prayer-to-the-dynamo-johannsson-13031">Deutsche Grammophon</a>]</li>
<li>Joanna Brouk — Sounds of the Sea (1981) [<a href="https://numerogroup.com/products/sounds-of-the-sea">Numero</a>]</li>
<li>Laraaji — Segue To Infinity [<a href="https://laraajinumero.bandcamp.com/album/segue-to-infinity">Numero</a>]</li>
<li>Roger Eno — The Turning Year: Rarities [<a href="https://www.rogereno.com/product/798403/the-turning-year-rarities">Deutsche Grammophon</a>]</li>
<li>Sarah Davachi — Selected Works I + II [<a href="https://sarahdavachi.bandcamp.com/album/selected-works-i-ii">Late Music/Disciples</a>]</li>
<li>Sparklehorse — Bird Machine [<a href="https://sparklehorse.bandcamp.com/album/bird-machine">Anti</a>]</li>
<li>William Basinski — The Clocktower at the Beach (1979) [<a href="https://lineimprint.bandcamp.com/album/the-clocktower-at-the-beach-1979">Line</a>]</li>
</ol>
<p>Plus some individual tracks:</p>
<ul>
<li>The Antlers — <a href="https://theantlers.bandcamp.com/track/need-nothing">Need Nothing</a></li>
<li>Björk ft. Rosalía — <a href="https://www.youtube.com/watch?v=8jsi2Tgvx6A">Oral</a></li>
<li>Depeche Mode — <a href="https://www.youtube.com/watch?v=iIyrLRixMs8">Ghosts Again</a> (from <em>Memento Mori</em>)</li>
<li>☞ Four Tet — <a href="https://www.youtube.com/watch?v=DGaKVLFNWzs">Three Drums</a></li>
<li>Oneohtrix Point Never — <a href="https://www.youtube.com/watch?v=_kyFqe36BqM">A Barely Lit Path</a> (from <em>Again</em>)</li>
<li>Roger Eno, Cecily Eno, Lotti Eno — <a href="https://www.youtube.com/watch?v=sWZi4ml1a4k">Bells (With Voices)</a> (from <em>The Turning Year: Rarities</em>)</li>
</ul>
<p>Live shows I went to this year: 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.</p>
<hr />
<p><q><a href="https://open.spotify.com/playlist/31OIme0YdF4ORWvEdTyE6V">Ryuichi’s Last Playlist</a>. 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.</q> — <abbr>RIP</abbr> Ryuichi Sakamoto.</p>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>Other 2023 lists: <a href="https://boomkat.com/charts/boomkat-end-of-year-charts-2023">Boomkat</a>, <a href="https://bleep.com/top-10-albums-of-the-year-2023">Bleep</a>, <a href="https://www.roughtrade.com/en-gb/collection/albums-of-the-year-2023">Rough Trade UK</a>, <a href="https://www.roughtrade.com/en-us/collection/albums-of-the-year-2023">Rough Trade US</a>, <a href="https://thequietus.com/articles/33662-the-quietus-top-100-albums-of-2023-norman-records">The Quietus</a>, <a href="https://www.thewire.co.uk/audio/tracks/the-wire-s-releases-of-the-year-2023">The Wire</a>. <a href="https://colly.com/articles/twenty-twentythree-in-music">Simon Collison</a>, <a href="https://hicks.design/journal/hicks-music-of-2023">Jon Hicks</a>.</p>
So you want to add a web feed2023-10-07T00:00:00Zhttps://danburzo.ro/add-a-web-feed/<p>You’ve decided you want a <a href="https://en.wikipedia.org/wiki/Web_feed">web feed</a> for your website’s content.</p>
<p>Some popular <abbr>CMS</abbr>es have you covered with built-in feeds. For WordPress, an <abbr>RSS</abbr> feed is available by default at <code>your-website.com/feed</code>, so it’s just a matter of <a href="https://danburzo.ro/add-a-web-feed/#link-to-feed">making the feed visible</a> in your <abbr>HTML</abbr> templates and you’re good to go.</p>
<p>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.</p>
<h2>Use the Atom format</h2>
<p>The two most popular web feed formats are <a href="https://en.wikipedia.org/wiki/RSS"><abbr>RSS</abbr></a> and <a href="https://en.wikipedia.org/wiki/Atom_(web_standard)">Atom</a>, both <abbr>XML</abbr>-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:</p>
<pre class="language-xml"><code class="language-xml"><span class="token prolog"><?xml version="1.0" encoding="utf-8"?></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>feed</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2005/Atom<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>Dan Burzo: Posts<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://danburzo.ro/posts.xml<span class="token punctuation">"</span></span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>self<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://danburzo.ro/<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>updated</span><span class="token punctuation">></span></span>2023-10-07T00:00:00Z<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>updated</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span><span class="token punctuation">></span></span>https://danburzo.ro/<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>id</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>author</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>name</span><span class="token punctuation">></span></span>Dan Burzo<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>name</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>author</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>entry</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span><span class="token punctuation">></span></span>https://danburzo.ro/add-a-web-feed/<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>id</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>So you want to add a web feed for your website<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://danburzo.ro/add-a-web-feed/<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>updated</span><span class="token punctuation">></span></span>2023-10-07T00:00:00Z<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>updated</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>content</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>html<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- entry content goes here --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>content</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>entry</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>feed</span><span class="token punctuation">></span></span></code></pre>
<p>The feed has a set of fields at the top level, and another set of fields for each <code><entry></code>. The guidelines below apply to both.</p>
<h3>Good <abbr>ID</abbr>s</h3>
<p><abbr>ID</abbr>s are used to identify the feed and the entries. Feed readers use the entry <abbr>ID</abbr> to remember when you’ve read or starred a post. Use the post’s <abbr>URL</abbr> as the <abbr>ID</abbr> for the corresponding entry, and the website’s <abbr>URL</abbr> as the feed <abbr>ID</abbr>, and then <a href="https://www.w3.org/Provider/Style/URI.html">try to never change them</a>.</p>
<h3>Date format</h3>
<p>There’s a dizzying array of standard date/time formats, as shown in <a href="https://ijmacd.github.io/rfc3339-iso8601/">this diagram</a> that illustrates <abbr>RFC 3339</abbr> vs. <abbr>ISO 8601</abbr> vs. <abbr>W3C</abbr> formats. Atom narrows it down significantly: all dates in the feed must be in <abbr>RFC 3339</abbr> date-time format with uppercase delimiters. That means we need all these stringed together:</p>
<ol>
<li>The date in <code>YYYY-MM-DD</code> format, eg. <code>2023-10-07</code></li>
<li>The uppercase <code>T</code> separator</li>
<li>The time in <code>HH:MM:SS</code> format, eg. <code>21:30:00</code></li>
<li>Either the timezone offset, eg. <code>+03:00</code>, or uppercase <code>Z</code> for <abbr>UTC</abbr> time.</li>
</ol>
<p>For example: <span style="color: var(--c-accent1);">October 7th, 2023</span> at 21:30, <abbr>UTC</abbr> time is expressed as <code>2023-10-07T21:30:00Z</code>.</p>
<p>The one date element that’s required for an entry is <code><updated></code>. You can distinguish the initial publish date from the last update date by mixing in a <code><published></code> element. On the top-level feed, <code><updated></code> is the date of the latest change to any of the entries.</p>
<h3><abbr>HTML</abbr> content</h3>
<p>Since <abbr>XML</abbr> and <abbr>HTML</abbr> share the syntax to some extent, adding raw <abbr>HTML</abbr> to the <code><content></code> element will trip up the <abbr>XML</abbr> parser. To prevent it, you can escape the <abbr>XML</abbr>-sensitive characters <code><</code>, <code>></code>, and <code>&</code> to their named entities <code>&lt;</code>, <code>&gt;</code>, and <code>&amp;</code> respectively.</p>
<p>Or, skip the text processing and wrap the <abbr>HTML</abbr> content as-is in a character data (<abbr>CDATA</abbr>) section:</p>
<pre class="language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>content</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>html<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token cdata"><![CDATA[ html content goes here ]]></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>content</span><span class="token punctuation">></span></span></code></pre>
<p>Careful: even ‘plain-text’ fields such as <code><title></code> can break <abbr>XML</abbr> parsing if an unaccounted-for ampersand or <code><</code> ends up in it, so it’s a good idea to handle it similarly to <code><content></code>.</p>
<h3>Absolute links</h3>
<p>All links to resources must use absolute <abbr>URL</abbr>s, or the feed must contain attributes that help feed readers resolve any relative <abbr>URL</abbr>s they encounter.</p>
<p>Technically, the Atom specification allows the <code>xml:base</code> attribute on any feed element to define the base <abbr>URL</abbr> against which to resolve relative <abbr>URL</abbr>s within the scope of that element:</p>
<pre class="language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>content</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>html<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">xml:</span>base</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://danburzo.ro/add-a-web-feed/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /><span class="token cdata"><![CDATA[ <br /> html content goes here <br />]]></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>content</span><span class="token punctuation">></span></span></code></pre>
<p>How well <code>xml:base</code> works in practice <a href="https://github.com/Ranchero-Software/NetNewsWire/issues/3662">depends on the feed reader</a>. For best compatibility, put absolute <abbr>URL</abbr>s everywhere, to the extent that your setup permits: the links to entries and the feed itself, and for all resources in the <abbr>HTML</abbr> content.</p>
<p>Replacing relative <abbr>URL</abbr>s with absolute counterparts in <abbr>HTML</abbr> content is not straightforward: it’s not just the <code>href</code>s and the <code>src</code>s, but things like <code>srcset</code> that have their own little syntax going on. So it’s worth noting that feed readers are free to look at the entry’s <code><link></code>, along with the <code>xml:base</code> attribute, to resolve relative <abbr>URL</abbr>s. As long as everything higher level uses absolute <abbr>URL</abbr>s, you’re probably fine to ship relative <abbr>URL</abbr> inside the <code><content></code>.</p>
<h2>Check that the feed is valid</h2>
<p>You can paste your Atom feed content into the <a href="https://validator.w3.org/feed/"><abbr>W3C</abbr> Feed Validator</a> to check that everything has been generated correctly, or get very good guidance on fixing any error it runs into.</p>
<p>After you’ve published your feed, add it to your feed reader to keep an eye. I use <a href="https://netnewswire.com/">NetNewsWire</a> 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.</p>
<h2>Put the feed on your server</h2>
<p>Depending on your setup, the feed may be generated on the fly on a dedicated <abbr>URL</abbr> as with WordPress’s <code>/feed/</code>, or stored as a physical file such as <code>posts.xml</code>.</p>
<p>The correct media type for Atom feeds is <code>application/atom+xml</code>, and you may have your server set the appropriate <code>Content-Type</code> response header. Doing so provokes both useful and somewhat annoying behaviors for visitors depending on their browser, so it’s not a wholehearted recommendation.</p>
<p>In the case of a physical <abbr>XML</abbr> file such as <code>posts.xml</code>, I find it works fine to serve it as a regular <code>application/xml</code> file. Feed readers will figure it out.</p>
<h2 id="link-to-feed">Link to the feed in <abbr>HTML</abbr></h2>
<p>Now that the feed exists, all that’s left is to link to it. You can add a <code><link rel=alternate></code> element in <code><head></code>, with the appropriate media type. This enables feed readers to extract the feed <abbr>URL</abbr> from the web page, making it easiers for visitors to subscribe.</p>
<pre class="language-html"><code class="language-html"><span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">doctype</span> <span class="token name">html</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>Dan Burzo<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>alternate<span class="token punctuation">'</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>application/atom+xml<span class="token punctuation">'</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>https://danburzo.ro/posts.xml<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span></code></pre>
<p>Although you can use more than one feed <code><link></code>, 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.</p>
<p>For better discoverability, even if the feed refers to the <code>/blog</code> section of your website, put the <code><link></code> 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.</p>
<p>Also include visible links to feeds in your site’s footer, labeled as such to make it findable with the browser’s search function:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>https://danburzo.ro/posts.xml<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>Feed (Atom)<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span></code></pre>
<p>With these in place, man and machine alike can find your feeds.</p>
<h2>Further reading</h2>
<ul>
<li><a href="https://datatracker.ietf.org/doc/html/rfc4287"><abbr>RFC 4287</abbr>: The Atom Syndication Format</a></li>
<li><a href="https://kevincox.ca/2022/05/06/rss-feed-best-practices/"><abbr>RSS</abbr> Feed Best Practises</a> by Kevin Cox</li>
<li><a href="https://validator.w3.org/feed/docs/atom.html">Introduction to Atom</a> from the <abbr>W3C</abbr> Feed Validator</li>
</ul>
Slotted content in Eleventy2023-02-28T00:00:00Zhttps://danburzo.ro/eleventy-slotted-content/<p>Some types of template data are awkward to maintain in <a href="https://www.11ty.dev/docs/data-cascade/">any of the many places</a> from where Eleventy can read it. Markdown's front-matter data can hold simple pieces of information just fine, but becomes unwieldy for rich content.</p>
<p>If you've ever wanted to <a href="https://daverupert.com/2021/01/art-direction-for-static-sites/">art-direct individual pages</a> with custom styles defined inline, I'm sure you're not exactly thrilled with front-loading a wall of CSS-in-YAML:</p>
<pre class="language-md"><code class="language-md"><span class="token front-matter-block"><span class="token punctuation">---</span><br /><span class="token front-matter yaml language-yaml">title: I wrote this on my portable typewriter<br />custom_style: |<br /> <style type='text/css'><br /> body { font-family: monospace; }<br /> </style></span><br /><span class="token punctuation">---</span></span></code></pre>
<p>The front-matter approach is workable, but has some drawbacks:</p>
<ul>
<li>you have to abide by weird YAML whitespace rules</li>
<li>there's no syntax highlighting</li>
<li>it places implementation details before the actual content</li>
</ul>
<p>One solution to these annoyances, of which I'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.</p>
<h2>Adding slotted content to Eleventy</h2>
<p>Say you'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.</p>
<p>Let's create a <code>{% slot %}</code> <a href="https://www.11ty.dev/docs/shortcodes/">shortcode</a> to define the recipe parts:</p>
<pre class="language-md"><code class="language-md"><span class="token front-matter-block"><span class="token punctuation">---</span><br /><span class="token front-matter yaml language-yaml">title: 'Best pesto of your life'<br />layout: layouts/recipe.njk</span><br /><span class="token punctuation">---</span></span><br /><br />{% slot 'ingredients' %}<br /><br /><span class="token list punctuation">*</span> fresh basil<br /><span class="token list punctuation">*</span> pine nuts<br /><span class="token list punctuation">*</span> olive oil<br /><span class="token list punctuation">*</span> pecorino<br /><span class="token list punctuation">*</span> garlic clove<br /><span class="token list punctuation">*</span> salt<br /><br />{% endslot %}<br /><br />{% slot 'instructions' %}<br /><br /><span class="token list punctuation">1.</span> Wash the basil leaves<br /><span class="token list punctuation">2.</span> Grate the pecorino<br /><span class="token list punctuation">3.</span> ...<br /><br />{% endslot %}<br /></code></pre>
<p>...that we can then place in the appropriate spots in the HTML layout:</p>
<pre class="language-twig"><code class="language-twig"><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>article</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>layout<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>ingredients<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span>Ingredients<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> slots<span class="token punctuation">.</span>ingredients <span class="token operator">|</span> safe <span class="token delimiter punctuation">}}</span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>instructions<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span>Instructions<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span> <br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> slots<span class="token punctuation">.</span>instructions <span class="token operator">|</span> safe <span class="token delimiter punctuation">}}</span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>article</span><span class="token punctuation">></span></span><br /> </code></pre>
<h3>Step 1: Add <code>slots</code> data to each page</h3>
<p>We're going to store all slots for all pages in the <code>slots</code> object, which acts as a map-of-maps keyed by the page's <code>inputPath</code>. Each page has access to its own slots via Eleventy's <a href="https://www.11ty.dev/docs/data-computed/">Computed Data</a> feature:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* .eleventy.js */</span><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> slots <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> config<span class="token punctuation">.</span><span class="token function">addGlobalData</span><span class="token punctuation">(</span><span class="token string">'eleventyComputed.slots'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> key <span class="token operator">=</span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">;</span><br /> slots<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> slots<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> slots<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h3>Step 2: Register the <code>slot</code> shortcode</h3>
<p>To pick up slot values from the Markdown files, we register the <code>slot</code> paired shortcode.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* .eleventy.js */</span><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> config<span class="token punctuation">.</span><span class="token function">addPairedShortcode</span><span class="token punctuation">(</span><span class="token string">'slot'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">content<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>name<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Missing name for {% slot %} block!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> slots<span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">]</span><span class="token punctuation">[</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> content<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>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.</p>
<p>The content of <code>{% slot 'ingredients' %}</code> can then be used as <code>{{ slots.ingredients }}</code> anywhere in the HTML layout.</p>
<p>The <code>throw</code> clause at the beginning guards against a common error. You may expect <code>{% slot ingredients %}</code> to define a slot named <code>'ingredients'</code>, but <code>ingredients</code> in the context of an Eleventy shortcode is <em>an identifier</em>, and what the shortcode gets as the <code>name</code> argument is the <em>value</em> bound to that identifier. The slot name needs to be quoted as <code>{% slot 'ingredients' %}</code>. Checking for an empty <code>name</code> doesn't make the shortcode error-proof, but it will cover this frequent typo.</p>
<h3>Step 3: Use the Eleventy Render plugin</h3>
<p>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's <a href="https://www.11ty.dev/docs/plugins/render/">Render plugin</a>, which registers a helpful <code>renderTemplate</code> shortcode.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* .eleventy.js */</span><br /><span class="token keyword">const</span> <span class="token punctuation">{</span> EleventyRenderPlugin <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@11ty/eleventy'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> config<span class="token punctuation">.</span><span class="token function">addPlugin</span><span class="token punctuation">(</span>EleventyRenderPlugin<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Change the HTML layout to render slot content as Nunjucks + Markdown and you're good to go:</p>
<pre class="language-twig"><code class="language-twig"><span class="token comment"><!-- Before: --></span> <br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>ingredients<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span>Ingredients<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> slots<span class="token punctuation">.</span>instructions <span class="token delimiter punctuation">}}</span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><br /><span class="token comment"><!-- After: --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>ingredients<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span>Ingredients<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">renderTemplate</span> <span class="token string"><span class="token punctuation">'</span>njk,md<span class="token punctuation">'</span></span><span class="token punctuation">,</span> slots <span class="token delimiter punctuation">%}</span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> ingredients <span class="token operator">|</span> safe <span class="token delimiter punctuation">}}</span></span> <br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endrenderTemplate</span> <span class="token delimiter punctuation">%}</span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /></code></pre>
<p>Note that by default content inside the <code>renderTemplate</code> shortcode only has access to the <code>page</code> and <code>eleventy</code> objects, so <code>slots</code> needs to be passed in as <a href="https://www.11ty.dev/docs/plugins/render/#pass-in-data">additional data</a>.</p>
<h2>Conclusion</h2>
<p>Nunjucks, like other templating languages of <a href="https://jinja.palletsprojects.com/en/3.1.x/">Jinja</a> lineage, technically comes with <a href="https://mozilla.github.io/nunjucks/templating.html#template-inheritance">template inheritance</a> via the <code>block</code> tag. It would make a lot of sense to <a href="https://github.com/11ty/eleventy/issues/685">define slotted content with named blocks</a>. However, Nunjuck's template inheritance only applies to templates that <code>extend</code> other templates, and a Markdown file rendered with Nunjucks does not extend its layout file.</p>
<p>In the absence of the built-in template inheritance, this is the most concise implementation for Markdown slotted content in Eleventy I could <del>come up with</del> <a href="https://knowyourmeme.com/memes/i-made-this">lift wholesale</a> from <a href="https://github.com/11ty/eleventy-plugin-bundle"><code>eleventy-plugin-bundle</code></a>. If it can be further simplified, I'd love to know.</p>
Sass in Eleventy, with versioning2023-02-02T00:00:00Zhttps://danburzo.ro/eleventy-sass/<p>There are many approaches to adding <a href="https://sass-lang.com/">Sass</a> support in Eleventy, and several plugins to abstract away these approaches. <a href="https://www.11ty.dev/docs/plugins/">The docs page</a> alone features four separate Sass plugins. When it comes to asset versioning, <em>how</em> you integrate Sass and Eleventy makes all the difference to the development experience.</p>
<p>I've spent the day tweaking the setup for Eleventy 2.0 to work with content-hashed <code>.scss</code> files for a new project. Let me walk you through it.</p>
<h2>Producing content hashes</h2>
<p>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 <code>style.fc3ff98e.css</code> are not an uncommon sight across the web.</p>
<p>Here's how to produce an 8-character hash for a given string in Node.js:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> createHash <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'node:crypto'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">getHash</span><span class="token punctuation">(</span><span class="token parameter">content<span class="token punctuation">,</span> length <span class="token operator">=</span> <span class="token number">8</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">createHash</span><span class="token punctuation">(</span><span class="token string">'md5'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">'hex'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">substr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> length<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token function">getHash</span><span class="token punctuation">(</span><span class="token string">"Hello world!"</span><span class="token punctuation">)</span><br /><span class="token comment">// => '86fb269d'</span><br /><br /><span class="token function">getHash</span><span class="token punctuation">(</span><span class="token string">'Hello back!'</span><span class="token punctuation">)</span><br /><span class="token comment">// => '7f4b5e8b'</span></code></pre>
<p>Different string, different hash. Ship it!</p>
<h2>Adding support for Sass with content hashing in Eleventy</h2>
<p>The official docs include sample code for <a href="https://www.11ty.dev/docs/languages/custom/#example-add-sass-support-to-eleventy">adding support for Sass to Eleventy</a>, paraphrased below:</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> config<span class="token punctuation">.</span><span class="token function">addTemplateFormats</span><span class="token punctuation">(</span><span class="token string">"scss"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> config<span class="token punctuation">.</span><span class="token function">addExtension</span><span class="token punctuation">(</span><span class="token string">"scss"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">outputFileExtension</span><span class="token operator">:</span> <span class="token string">"css"</span><span class="token punctuation">,</span><br /> <span class="token function-variable function">compile</span><span class="token operator">:</span> <span class="token parameter">content</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> <span class="token punctuation">{</span> css <span class="token punctuation">}</span> <span class="token operator">=</span> sass<span class="token punctuation">.</span><span class="token function">compileString</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token parameter">data</span> <span class="token operator">=></span> css<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This short snippet sets up a basic workflow to transform all <code>.scss</code> files from the input directory to <code>.css</code>, but <a href="https://github.com/11ty/eleventy/discussions/2786">there's a gotcha</a>. Permalinks for the resulting <code>.css</code> files are generated <em>before</em> the <code>compile()</code> function has chance to run, so we can't extend it to produce permalinks based on file contents.</p>
<p>Instead, Sass processing needs to happen earlier in the build process, in the <code>getData()</code> method. Based on its <code>inputPath</code>, <code>sass</code> can read the file directly and populate the template's <code>data</code> object. The <code>compile()</code> and <code>compileOptions.permalink()</code> methods then simply pick up the bits they're interested in.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> sass <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'node:path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Watch for changes in .scss files.<br /> */</span><br /> config<span class="token punctuation">.</span><span class="token function">addTemplateFormats</span><span class="token punctuation">(</span><span class="token string">'scss'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/*<br /> Define how to process .scss files.<br /> */</span><br /> config<span class="token punctuation">.</span><span class="token function">addExtension</span><span class="token punctuation">(</span><span class="token string">'scss'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> We're feeding the `inputPath` to Sass directly, so we don't need Eleventy to read the content of `.scss` files.<br /> */</span><br /> <span class="token literal-property property">read</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /><br /> <span class="token comment">/*<br /> Produce the data for each `.scss` file, including its processed CSS content and its MD5 content hash.<br /> */</span><br /> <span class="token function-variable function">getData</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">inputPath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Exclude .scss files from `collections.all` so they don't show up in sitemaps, RSS feeds, etc.<br /> */</span><br /> <span class="token literal-property property">eleventyExcludeFromCollections</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token comment">/*<br /> Don't process .scss files that start with an underscore as standalone.<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'_'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> data<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> css <span class="token punctuation">}</span> <span class="token operator">=</span> sass<span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> data<span class="token punctuation">.</span>_content <span class="token operator">=</span> css<span class="token punctuation">;</span><br /> data<span class="token punctuation">.</span>_hash <span class="token operator">=</span> <span class="token function">getHash</span><span class="token punctuation">(</span>css<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> data<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">compileOptions</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* <br /> Disable caching of `.scss` files, for good measure.<br /> */</span><br /> <span class="token literal-property property">cache</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token function-variable function">permalink</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">permalink<span class="token punctuation">,</span> inputPath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Don't output .scss files that start with an underscore, as per Sass conventions…<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'_'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token comment">/*<br /> …and for other .scss files include the MD5 content hash produced in the `.getData()` method in the output file path.<br /> */</span><br /> <span class="token keyword">return</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>filePathStem<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token punctuation">.</span>_hash<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.css</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token comment">/*<br /> Read the processed CSS content from the data object produced with `.getData()`.<br /> */</span><br /> <span class="token function-variable function">compile</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token parameter">data</span> <span class="token operator">=></span> data<span class="token punctuation">.</span>_content<br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>So we've placed the content hashes in the permalinks of generated <code>.css</code> files. To retrieve these hashed permalinks inside HTML templates, we prepare an input/output map using <a href="https://www.11ty.dev/docs/config/#transforms">a transform</a>, which helpfully runs through each input file. As a small convenience, we strip the input directory (<code>src</code> in the snippet below) from the beginning of input paths.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> outputMap <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br />config<span class="token punctuation">.</span><span class="token function">addTransform</span><span class="token punctuation">(</span><span class="token string">'outputMap'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">content</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> filepath <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">relative</span><span class="token punctuation">(</span><span class="token string">'src'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> outputMap<span class="token punctuation">[</span>filepath<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>page<span class="token punctuation">.</span>url<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> content<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Put <code>outputMap</code> in a filter to look up versioned URLs for your input files and you're good to go.</p>
<pre class="language-js"><code class="language-js">config<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">'hashed'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">filepath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>outputMap<span class="token punctuation">[</span>filepath<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hashed: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filepath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> not found in map.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> outputMap<span class="token punctuation">[</span>filepath<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Here's how to use the <code>hashed</code> filter in a template to obtain the versioned URL for <code>src/_assets/style.scss</code>:</p>
<pre class="language-twig"><code class="language-twig"><span class="token comment"><!-- This… --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <br /> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>stylesheet<span class="token punctuation">'</span></span> <br /> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>text/css<span class="token punctuation">'</span></span> <br /> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> <span class="token string"><span class="token punctuation">'</span>_assets/style.scss<span class="token punctuation">'</span></span> <span class="token operator">|</span> hashed <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">'</span></span><br /><span class="token punctuation">></span></span><br /><br /><span class="token comment"><!-- …turns to this --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <br /> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>stylesheet<span class="token punctuation">'</span></span> <br /> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>text/css<span class="token punctuation">'</span></span> <br /> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>_assets/style.c8ad33ff.css<span class="token punctuation">'</span></span><br /><span class="token punctuation">></span></span><br /></code></pre>
<h2>Conclusion</h2>
<p>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's short enough to plop it straight into your <code>.eleventy.js</code> config:</p>
<details>
<summary>Full listing: add support for Sass with content hashing in Eleventy</summary>
<p>Here's the full <strong>.eleventy.js</strong> configuration file:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> sass <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'node:path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">{</span> createHash <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'node:crypto'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/*<br /> For the given `content` string, <br /> generate an MD5 hash of `length` chars.<br /> */</span><br /><span class="token keyword">function</span> <span class="token function">getHash</span><span class="token punctuation">(</span><span class="token parameter">content<span class="token punctuation">,</span> length <span class="token operator">=</span> <span class="token number">8</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">createHash</span><span class="token punctuation">(</span><span class="token string">'md5'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">'hex'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">substr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> length<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /><br /> <span class="token comment">/*<br /> Watch for changes in .scss files.<br /> */</span><br /> config<span class="token punctuation">.</span><span class="token function">addTemplateFormats</span><span class="token punctuation">(</span><span class="token string">'scss'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/*<br /> Define how to process .scss files.<br /> */</span><br /> config<span class="token punctuation">.</span><span class="token function">addExtension</span><span class="token punctuation">(</span><span class="token string">'scss'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> We're feeding the `inputPath` to Sass directly, so we don't need Eleventy to read the content of `.scss` files.<br /> */</span><br /> <span class="token literal-property property">read</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /><br /> <span class="token comment">/*<br /> Produce the data for each `.scss` file, including its processed CSS content and its MD5 content hash.<br /> */</span><br /> <span class="token function-variable function">getData</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">inputPath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Don't process .scss files that start with an underscore as standalone.<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'_'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> css <span class="token punctuation">}</span> <span class="token operator">=</span> sass<span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Exclude .scss files from `collections.all` so they don't show up in sitemaps, RSS feeds, etc.<br /> */</span><br /> <span class="token literal-property property">eleventyExcludeFromCollections</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">_content</span><span class="token operator">:</span> css<span class="token punctuation">,</span><br /> <span class="token literal-property property">_hash</span><span class="token operator">:</span> <span class="token function">getHash</span><span class="token punctuation">(</span>css<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">compileOptions</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* <br /> Disable caching of `.scss` files, for good measure.<br /> */</span><br /> <span class="token literal-property property">cache</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token function-variable function">permalink</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">permalink<span class="token punctuation">,</span> inputPath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Don't output .scss files that start with an underscore, as per Sass conventions…<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'_'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token comment">/*<br /> …and for other .scss files include the MD5 content hash produced in the `.getData()` method in the output file path.<br /> */</span><br /> <span class="token keyword">return</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>filePathStem<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token punctuation">.</span>_hash<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.css</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token comment">/*<br /> Read the processed CSS content from the data object produced with `.getData()`.<br /> */</span><br /> <span class="token function-variable function">compile</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token parameter">data</span> <span class="token operator">=></span> data<span class="token punctuation">.</span>_content<br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> outputMap <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> config<span class="token punctuation">.</span><span class="token function">addTransform</span><span class="token punctuation">(</span><span class="token string">'outputMap'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">content</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> filepath <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">relative</span><span class="token punctuation">(</span><span class="token string">'src'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> outputMap<span class="token punctuation">[</span>filepath<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>page<span class="token punctuation">.</span>url<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> content<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> config<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">'hashed'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">filepath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>outputMap<span class="token punctuation">[</span>filepath<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hashed: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filepath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> not found in map.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> outputMap<span class="token punctuation">[</span>filepath<span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">markdownTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">dataTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">htmlTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">dir</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">'src'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token string">'dist'</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
</details>
<p>If you prefer the plugin route, it looks like <a href="https://github.com/kentaroi/eleventy-sass/">eleventy-sass</a> and its companion <a href="https://github.com/kentaroi/eleventy-plugin-rev">eleventy-plugin-rev</a> do a similar job.</p>
<h2 id="addenda">Addenda</h2>
<p>A few hours after first publishing this article, <a href="https://github.com/11ty/eleventy-plugin-bundle">@11ty/eleventy-plugin-bundle</a> was released to help you <q>create minimal per-page or app-level bundles of CSS, JavaScript, or HTML to be included in your Eleventy project</q>.</p>
<p><strong>Update March 1, 2023:</strong> <code>.scss</code> partials (i.e. files starting with an underscore) should not be processed at all in <code>getData()</code>. They'll be processed when they're included in regular <code>.scss</code> files.</p>
<p><strong>Update August 9, 2023:</strong> <code>.scss</code> partials should likewise not be included in collections, so they don’t show up in RSS, sitemaps, etc.</p>
Line-height tricks made simpler with the ‘lh’ CSS unit2023-01-13T00:00:00Zhttps://danburzo.ro/line-height-lh/<p>With Safari Technology Preview having supported it for a while, and Chromium 109 enabling user-facing support in a first batch of browsers, it's time to see a few ways in which the <code>lh</code> CSS unit is a useful addition.</p>
<p>The <code>lh</code> unit is formally described as follows:</p>
<figure>
<blockquote>
<p>Equal to the computed value of the line-height property of the element on which it is used, converting <code>normal</code> to an absolute length by using only the metrics of the first available font.</p>
</blockquote>
<blockquote>
<p>When used in the value of the <code>font-size</code> 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 <code>font</code> and <code>line-height</code> properties, if the element has no parent. Likewise, when <code>lh</code> or <code>rlh</code> units are used in the value of the <code>line-height</code> property on the element they refer to, they resolve against the computed <code>line-height</code> and <code>font</code> metrics of the parent element — or the computed metrics corresponding to the initial values of the <code>font</code> and <code>line-height</code> properties, if the element has no parent.</p>
</blockquote>
<figcaption>
<p>Description of the <code>lh</code> unit in the <em>6.1.1. Font-relative Lengths</em> section of <cite><a href="https://drafts.csswg.org/css-values-4/#font-relative-lengths">CSS Values and Units Module Level 4</a> (Editor's Draft)</cite>.</p>
</figcaption>
</figure>
<p>More concisely, <code>1lh</code> is the computed line height of:</p>
<ul>
<li>the parent when used in <code>font-size</code> or <code>line-height</code> declarations, or</li>
<li>of the element itself in any other declaration.</li>
</ul>
<h2>Uses for the <code>lh</code> unit</h2>
<div class="lh-support-notice">
<blockquote>
<p><strong>Note:</strong> The CSS examples below rely on support for the <code>lh</code> unit, which your current browser does not provide. To keep the snippets clearer, I have not included any style fallbacks, so the examples won't look great. Load this article in Safari TP, Chrome 109+, or Edge 109+ to see the effects in action.</p>
</blockquote>
</div>
<h3>Ruled paper effect</h3>
<p>The <code>lh</code> 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 <code>1lh</code>, which we achieve here with <code>repeating-linear-gradient()</code>.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.index-card</span> <span class="token punctuation">{</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid<span class="token punctuation">;</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> gold<span class="token punctuation">;</span><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span><br /> transparent 0 <span class="token function">calc</span><span class="token punctuation">(</span>1lh - 1px<span class="token punctuation">)</span><span class="token punctuation">,</span> <br /> midnightblue <span class="token function">calc</span><span class="token punctuation">(</span>1lh - 1px<span class="token punctuation">)</span> 1lh<br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">/*<br /> Aligning the background image to the content-box<br /> lets us use any padding on the element.<br /> */</span><br /> <span class="token property">background-origin</span><span class="token punctuation">:</span> content-box<span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<blockquote class="index-card" lang="ro">
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ă.
</blockquote>
<p>This was not impossible to do before. When <code>line-height</code> is a unitless multiplier of the font's size, we can produce a custom CSS property <code>var(--lh)</code> with the same <code>1lh</code>:</p>
<pre class="language-css"><code class="language-css"><span class="token property">--line-height</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">;</span><br /><span class="token property">--lh</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>1em * <span class="token function">var</span><span class="token punctuation">(</span>--line-height<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--line-height<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This basic technique can be used for a variety of effects, such as Adam Argyle's <a href="https://codepen.io/argyleink/pen/QWrzKOg">text of a different color on each line</a>. It's also a good addition to the <a href="https://danburzo.ro/css-layout-debugger/">debugging toolbox</a>. Throughout the article, I'll use the <code>.index-card</code> CSS class on examples to make the line boxes visible.</p>
<p><strong>Note:</strong> In Safari TP, zooming in and out of the page affects the computed value of <code>1lh</code> inside CSS gradients, causing the ruled paper to shift out of alignment with the text [<a href="https://bugs.webkit.org/show_bug.cgi?id=252075">WebKit#252075</a>].</p>
<h3>Nicely sized inline icons</h3>
<p>The <code>lh</code> 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 <a href="https://css-tricks.com/lh-and-rlh-units/">CSS Tricks</a>):</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.with-icon:before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /> <span class="token property">height</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> midnightblue<span class="token punctuation">;</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span><br /> <span class="token property">vertical-align</span><span class="token punctuation">:</span> bottom<span class="token punctuation">;</span><br /> <span class="token property">margin-inline-end</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<blockquote class="index-card" lang="ro">
<span class="with-icon">O ușă se deschise</span>
</blockquote>
<p>Here, a pseudo-element of <code>height: 1lh</code> is anchored to either end of the line box using <code>vertical-align: top</code> or <code>bottom</code> to ensure perfect alignment.</p>
<p>Speaking of perfect, I should note that <code>1lh</code> refers to the <em>ideal line height</em> of an element, but the actual line-height can get bumped by things like inline blocks or <code><sup></code> elements:</p>
<blockquote class="index-card" lang="ro">
<span class="with-icon">O ușă<sup>1</sup> se deschise</span>
</blockquote>
<h3>Baseline grid one-liner</h3>
<blockquote>
<p><strong>Disclaimer / note to future self</strong>: A <code>line-height: <length>;</code> 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.</p>
</blockquote>
<p>I saved the best for last.</p>
<p>Beyond a simple abstraction over <code>calc(1em * var(--line-height))</code>, elements can be made to inherit their parent's line-height. Maintaining vertical rhythm for the entire document is reduced to an elegant declaration:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.6<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">html *</span> <span class="token punctuation">{</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>How cool is that? I mean sure, there'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 <a href="https://w3c.github.io/csswg-drafts/css-rhythm">CSS Rhythmic Sizing</a> specification, including a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/line-height-step"><code>line-height-step</code></a> property that's already available in Chromium under a run-time feature flag.</p>
<p>The implications of going document-wide for this need to be explored before we can declare we have another <code>* { box-sizing: border-box }</code>-style gold nugget on our hands. In the meantime, it works pretty well applied locally:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.baseline-grid</span> <span class="token punctuation">{</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.3<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.baseline-grid *</span> <span class="token punctuation">{</span><br /> <span class="token property">line-height</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /> <span class="token property">margin-block</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /> <span class="token property">text-indent</span><span class="token punctuation">:</span> 1lh<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.baseline-grid .smaller</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.8em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<blockquote class="index-card baseline-grid" lang="ro">
<p>Î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.</p>
<p class="smaller">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.</p>
<p>Î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.</p>
</blockquote>
<p>In this setup, <code>1lh</code> becomes a fixed measure, like <code>1rem</code>, that can be used to keep in sync margins, indents, and others.</p>
<p><strong>Note:</strong> In Safari TP, imposing a minimum font size via the browser setting affects <code>line-height: 1lh</code> [<a href="https://bugs.webkit.org/show_bug.cgi?id=252108">WebKit#252108</a>].</p>
<h2>Conclusion</h2>
<p>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 <code>lh</code> unit.</p>
<p>The text fragments are from M. Blecher's <em>Inimi cicatrizate</em> (<em>Scarred hearts</em>), available <a href="https://llll.ro/max-blecher/inimi-cicatrizate/">in the public domain</a>.</p>
Why do mobile browsers share canonical URLs?2023-01-10T00:00:00Zhttps://danburzo.ro/canonical-sharing/<p>It did it again the other day.</p>
<p>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'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?</p>
<p>Since it was not the first occurrence of the sort, I'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's <em>canonical URL</em> whenever it finds one. I'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.</p>
<h2>Some background on canonical URLs</h2>
<p>The canonical URL is meant to convey, as per the <a href="https://html.spec.whatwg.org/multipage/links.html#link-type-canonical">HTML spec</a>, the <q>preferred URL for the current document</q>. The introductory paragraphs from <a href="https://www.rfc-editor.org/rfc/rfc6596">RFC6596: The Canonical Link Relation</a> tell us most of what we need to know about its intent:</p>
<blockquote>
<p>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.</p>
<p>In regard to the link relation type, "canonical" can be described informally as the author'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'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.</p>
</blockquote>
<p>A canonical URL can be defined either through <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link">a <code>Link</code> HTTP header</a> or, more commonly, via a <code><link></code> HTML element:</p>
<pre class="language-html"><code class="language-html"><span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">doctype</span> <span class="token name">html</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>en<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>Welcome to my website<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>canonical<span class="token punctuation">'</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>https://danburzo.ro/<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span></code></pre>
<h2>How did browsers end up using canonical URLs?</h2>
<p>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 <a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls#why-it-matters">the benefits of canonical URLs</a>.</p>
<p>Sometime around 2017, this mechanism meant for machines was co-opted by browsers to address a very different problem: nudging users <a href="https://news.ycombinator.com/item?id=15085159">away from Google's AMP Viewer</a>, and towards the original web pages it was caching. Safari 11 for iOS was soon followed by <a href="https://chromereleases.googleblog.com/2018/01/chrome-for-android-update.html">Chrome 64 for Android</a> in favoring a page's canonical URL for certain interactions, such as sharing and bookmarking.</p>
<p>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.</p>
<p>However, this was released as a general mechanism that affects any page that uses <code><link rel=canonical></code>. That's <em>a lot</em> of pages, more than half of the pages analyzed by HTTP Archive for this year's <a href="https://almanac.httparchive.org/en/2022/seo#canonical-tags">Web Almanac</a>, including crowd favorite Wikipedia.org.</p>
<p>In the general case, the results are more of a mixed bag.</p>
<h3>The pros and cons of sharing canonical URLs</h3>
<p>Some effects are decidedly positive: various pieces of user tracking gunk are stripped from URLs before they're passed to friends, as these are generally not featured in the page'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's privacy.</p>
<p>But things that are beneficial to a search engine don't always match user needs. In fact, they can even clash.</p>
<p>Take filters on an e-commerce website as an example. For search engines, it'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.</p>
<p>For a user, on the other hand, the specific filtering criteria and sorting options are kind of the whole point, aren't they? When I browse my favorite bookstore's website for <samp>foreign books, in English, available in stock, sorted by most recent first</samp> 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.</p>
<p>Even if you do manage to devise a pattern that fulfills both user needs and SEO goals, you're not yet out of the woods. When using canonical URLs <em>in any form</em> on your website, you're implicitly signing up for:</p>
<ul>
<li>remaining vigilant about updating the <code><link rel=canonical></code> element in response to all operations that may alter the URL, such as with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/History">History API</a> via <code>pushState()</code> or <code>replaceState()</code>;</li>
<li>depending on the browser implementation, <a href="https://bugs.webkit.org/show_bug.cgi?id=250317">waving goodbye to using fragment identifiers</a>. Since it's exclusive to the client, the URL fragment can'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.</li>
</ul>
<p>Speaking of browser implementations, let's see how they stack up at the moment of writing.</p>
<h2>Current browser behavior</h2>
<blockquote>
<p><strong>Note</strong>: I'm using <a href="https://danburzo.ro/demos/canonical-link.html">this demo page</a> to test browser behavior, where the <em>original URL</em> is, depending on the things you click on, of the form <code>https://danburzo.ro/<wbr />demos/<wbr /><mark class="wavy">canonical-link.html<wbr />?hello=world#a</mark></code>. Its <em>canonical URL</em> is defined as <code>https://danburzo.ro/<wbr />demos/<wbr /><mark class="wavy">canonical-link-canonical.html</mark></code> via the <code><link rel=canonical></code> element. Notice the different HTML file name and lack of query string and fragment.</p>
</blockquote>
<p>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.</p>
<p>On Android:</p>
<ul>
<li><strong>Samsung Internet 19</strong> and <strong>Firefox Android 108</strong> share and bookmark the original, intact URL.</li>
<li><strong>Chrome Android 108</strong> 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 [<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1038187">Chromium#1038187</a>] that makes sharing canonical URLs a little less problematic.</li>
<li><strong>Edge Android 108</strong>: no data, as I couldn't find an <code>.apk</code> to load on the device (test data appreciated!)</li>
</ul>
<p>On iOS:</p>
<ul>
<li><strong>Firefox iOS 108</strong> shares and bookmarks the original URL.</li>
<li><strong>Safari iOS 16.2</strong> uses the canonical URL for both sharing and bookmarking but loses the fragment identifier from the original URL [<a href="https://bugs.webkit.org/show_bug.cgi?id=250317">WebKit#250317</a>], including the <a href="https://github.com/GoogleChromeLabs/link-to-text-fragment">text fragment links</a> for which Safari 16.1 has recently <a href="https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes">added support</a>.</li>
<li><strong>Chrome iOS 108</strong>, in an effort to match Safari [<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1323782">Chromium#1323782</a>], 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. (<em>Add to Reading List</em> is weird: it's made to match Safari's bookmarking behavior since it's accessed from the same sharing panel, even though the page gets added to Chrome's own Reading List.)</li>
<li><strong>Edge iOS 108</strong>, 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.</li>
</ul>
<table style="font-size: 0.9em">
<caption align="top">A summary of mobile browser behavior in regards to sharing and bookmarking URLs in the presence of a canonical link, January 2023.</caption>
<thead>
<th colspan="2">Browser</th>
<th>Shared URL</th>
<th>Bookmarked URL</th>
</thead>
<tbody>
<tr>
<th rowspan="4">Android</th>
<th>Firefox</th>
<td>Original</td>
<td>Original</td>
</tr>
<tr>
<th>Samsung Internet</th>
<td>Original</td>
<td>Original</td>
</tr>
<tr>
<th>Chrome</th>
<td>Canonical, fragment kept</td>
<td>Original</td>
</tr>
<tr>
<th>Edge</th>
<td><i>Missing data</i></td>
<td><i>Missing data</i></td>
</tr>
<tr>
<th rowspan="4">iOS</th>
<th>Firefox</th>
<td>Original</td>
<td>Original</td>
</tr>
<tr>
<th>Safari</th>
<td>Canonical, fragment lost</td>
<td>Canonical, fragment lost</td>
</tr>
<tr>
<th>Chrome</th>
<td>Canonical, fragment lost</td>
<td>Depends, see notes</td>
</tr>
<tr>
<th>Edge</th>
<td>Canonical, fragment lost</td>
<td>Original</td>
</tr>
</tbody>
</table>
<p>How do browser vendors feel about the status quo?</p>
<p>Chrome seems to get a steady stream of issue reports about the "wrong URL" being shared or copied to the clipboard — see, for example, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=799955">Chromium#799955</a>, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=924309">Chromium#924309</a> — to which the resolution is invariably <em>works as intended</em> 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 <code><link rel=canonical></code> by authors — see conversations in <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=988497">Chromium#988497</a>, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1202789">Chromium#1202789</a>, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1306663">Chromium#1306663</a> — than on any fundamental flaw or limitation with the approach itself, but it's a start.</p>
<p>Over at Mozilla arguments for, or against, using the canonical URL for sharing [<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1794879">Mozilla#1794879</a>] and bookmarking [<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=502418">Mozilla#502418</a>] have not yet truly taken off.</p>
<h2>Possible solutions</h2>
<p>I don't have much in the way of alternative solutions, but I tend to err on the side of user agency.</p>
<p>For each browser on each mobile platform, the sharing UI is flexible enough to accommodate a persistent user preference. Pictured below, Safari 16.2's sharing panel already has an <samp>Options</samp> section that could very well let you tweak how a link gets shared or bookmarked.</p>
<figure>
<img loading="lazy" src="https://danburzo.ro/img/canonical-sharing/safari-ios-sharing-url.png" alt="The iOS Safari sharing panel floats above the web page content. In the panel's header, underneath the page title and domain name, a button labeled Options." width="562" height="633" />
<figcaption>
<p>The sharing panel in Safari 16.2.</p>
</figcaption>
</figure>
<p>Finally, an invitation: I'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.</p>
<p><em>Thanks to Šime for the useful feedback.</em></p>
<hr />
<h2>Miscellaneous bits and bobs</h2>
<p>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 [<a href="https://bugs.webkit.org/show_bug.cgi?id=250319">WebKit#250319</a>], and that clicking an in-page link to a text fragment does not update the URL fragment [<a href="https://bugs.webkit.org/show_bug.cgi?id=250320">WebKit#250320</a>].</p>
Get useful input values with formDataMap()2023-01-08T00:00:00Zhttps://danburzo.ro/formdatamap/<p>Chris Ferdinandi's recent article for <em>12 Days of Web</em> called <a href="https://12daysofweb.dev/2022/formdata-api/">FormData API</a> reminded me about a helper function I wrote to quickly wire up plain HTML form controls in interactive demos of the <em>move slider make thing happen</em> type, without needing to reach for <a href="https://github.com/dataarts/dat.gui">dat.gui</a> or a similar library.</p>
<p>When your set of controls is organized as a plain HTML form, <a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">the <code>FormData</code> DOM interface</a> is a useful way to read the values. It also interacts nicely with other APIs, such as <code>fetch()</code> and <code>URLSearchParams</code>.</p>
<p>For our specific use case, it does present a couple of inconveniences:</p>
<ul>
<li>you read its entries via a dedicated API, with two separate methods <code>.get()</code> and <code>.getAll()</code> depending on whether you're expecting one value or many;</li>
<li>everything except <code>Blob</code>s is cast to a string.</li>
</ul>
<p>Let's take these minuscule concerns as an opportunity to implement a slightly-enhanced version of <code>FormData</code> of our own, that produces a plain old JavaScript object, and keeps the value types closer to their underlying form controls.</p>
<h2>Let's reimplement <code>FormData</code> for some reason</h2>
<p>The HTML specification helpfully <a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set">lists the steps</a> for constructing the entry list for a given form element. We want our implementation to be fairly robust against the markup you'd write today, but not necessarily cover the historical aspects; as such, the implementation is going to gloss over <code><input type='image'></code> with its <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input/image#using_the_x_and_y_data_points">very specific behavior</a>.</p>
<h3>Finding elements that should be submitted</h3>
<p>Form elements are grouped in overlapping categories:</p>
<ul>
<li><em>Submittable elements</em> are <code><button></code>, <code><input></code>, <code><select></code>, <code><textarea></code>, and any form-associated custom elements.</li>
<li><em>Listed elements</em> include <code><fieldset></code>, <code><object></code>, and <code><output></code>, in addition to submittable elements. They can have an explicit <code>form</code> attribute that associates them with a form somewhere else in the DOM tree.</li>
</ul>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements"><code>HTMLFormElement.elements</code></a> collection does the legwork of gathering the <em>listed elements</em> associated with the form, taking into account any explicit <code>form</code> attribute on the elements. The only work that leaves for our implementation is to filter out any element types that are <em>listed but not submittable</em>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">formDataMap</span><span class="token punctuation">(</span><span class="token parameter">form<span class="token punctuation">,</span> submitter</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> excludedTags <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'FIELDSET'</span><span class="token punctuation">,</span> <span class="token string">'OBJECT'</span><span class="token punctuation">,</span> <span class="token string">'OUTPUT'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> submittable <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>form<span class="token punctuation">.</span>elements<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">el</span> <span class="token operator">=></span> <span class="token operator">!</span>excludedTags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>tagName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Out of all submittable elements, only form controls that comply with a set of rules are actually submitted. Any such element must:</p>
<ul>
<li>have a non-empty <code>name</code> attribute (except for custom elements associated with the form, which <a href="https://danburzo.ro/formdatamap/#handling-custom-elements">in some cases can go without</a>);</li>
<li>not be disabled, either via its <code>disabled</code> attribute or through its position in the DOM tree;</li>
<li>not be nested inside a <code><datalist></code> element;</li>
<li>be checked, in the case of <code>radio</code> and <code>checkbox</code> inputs;</li>
<li>not be a button, except for the button that submitted the form, if applicable.</li>
</ul>
<p>These rules are merged into the <code>shouldSubmit(el)</code> function below:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">formDataMap</span><span class="token punctuation">(</span><span class="token parameter">form<span class="token punctuation">,</span> submitter</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /><br /> <span class="token keyword">const</span> excludedTags <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'FIELDSET'</span><span class="token punctuation">,</span> <span class="token string">'OBJECT'</span><span class="token punctuation">,</span> <span class="token string">'OUTPUT'</span><span class="token punctuation">]</span><br /> <span class="token keyword">const</span> excludedTypes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'button'</span><span class="token punctuation">,</span> <span class="token string">'reset'</span><span class="token punctuation">,</span> <span class="token string">'image'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">function</span> <span class="token function">shouldSubmit</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>el<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>excludedTags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>tagName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>excludedTypes<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'submit'</span> <span class="token operator">&&</span> el <span class="token operator">!==</span> submitter<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'radio'</span> <span class="token operator">&&</span> <span class="token operator">!</span>el<span class="token punctuation">.</span>checked<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'checkbox'</span> <span class="token operator">&&</span> <span class="token operator">!</span>el<span class="token punctuation">.</span>checked<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>disabled <span class="token operator">||</span> el<span class="token punctuation">.</span><span class="token function">matches</span><span class="token punctuation">(</span><span class="token string">':disabled'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span><span class="token function">closest</span><span class="token punctuation">(</span><span class="token string">'datalist'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">const</span> toSubmit <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>form<span class="token punctuation">.</span>elements<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>shouldSubmit<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Adding values based on the type of form control</h3>
<p>With the rules out of the way, on to the fun part of getting nice values based on the type of each element. Here's the plan:</p>
<ul>
<li><strong><code>String</code></strong>: most form controls work okay with a string value. For inputs of type <code>checkbox</code>, <code>color</code>, <code>email</code>, <code>hidden</code>, <code>password</code>, <code>radio</code>, <code>search</code>, <code>tel</code>, or <code>text</code>, as well as for <code><textarea></code> elements, the plain <code>element.value</code> suffices. Similarly, <code><select></code> elements produce strings.</li>
<li><strong><code>Number</code></strong> for inputs of types <code>number</code> and <code>range</code>.</li>
<li><strong><code>Date</code></strong> for inputs of type <code>date</code>, <code>datetime-local</code>; date-adjacent types such as <code>month</code>, <code>time</code>, and <code>week</code> are left as strings currently, but they can probably afford something more interesting.</li>
<li><strong><code>File</code></strong> for inputs of type <code>file</code>.</li>
<li><strong><code>URL</code></strong> for inputs of type <code>url</code>.</li>
</ul>
<p>Putting everything all together, here's the final function:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">formDataMap</span><span class="token punctuation">(</span><span class="token parameter">form<span class="token punctuation">,</span> submitter</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> excludedTags <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'FIELDSET'</span><span class="token punctuation">,</span> <span class="token string">'OBJECT'</span><span class="token punctuation">,</span> <span class="token string">'OUTPUT'</span><span class="token punctuation">]</span><br /> <span class="token keyword">const</span> excludedTypes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'button'</span><span class="token punctuation">,</span> <span class="token string">'reset'</span><span class="token punctuation">,</span> <span class="token string">'image'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">function</span> <span class="token function">shouldSubmit</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>el<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>excludedTags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>tagName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>excludedTypes<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'submit'</span> <span class="token operator">&&</span> el <span class="token operator">!==</span> submitter<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'radio'</span> <span class="token operator">&&</span> <span class="token operator">!</span>el<span class="token punctuation">.</span>checked<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'checkbox'</span> <span class="token operator">&&</span> <span class="token operator">!</span>el<span class="token punctuation">.</span>checked<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>disabled <span class="token operator">||</span> el<span class="token punctuation">.</span><span class="token function">matches</span><span class="token punctuation">(</span><span class="token string">':disabled'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span><span class="token function">closest</span><span class="token punctuation">(</span><span class="token string">'datalist'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">function</span> <span class="token function">append</span><span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> val</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> result<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> <br /> Object<span class="token punctuation">.</span><span class="token function">hasOwn</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> key<span class="token punctuation">)</span> <span class="token operator">?</span> <br /> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>result<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">,</span> val<span class="token punctuation">)</span> <br /> <span class="token operator">:</span> val<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>form<span class="token punctuation">.</span>elements<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">el</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">shouldSubmit</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> name<span class="token punctuation">,</span> type <span class="token punctuation">}</span> <span class="token operator">=</span> el<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'number'</span> <span class="token operator">||</span> type <span class="token operator">===</span> <span class="token string">'range'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">append</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token operator">+</span>el<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'date'</span> <span class="token operator">||</span> type <span class="token operator">===</span> <span class="token string">'datetime-local'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">append</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> el<span class="token punctuation">.</span><span class="token function">valueAsDate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'file'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">append</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> el<span class="token punctuation">.</span>files<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'url'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">append</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'select-one'</span> <span class="token operator">||</span> type <span class="token operator">===</span> <span class="token string">'select-multiple'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>selectedOptions<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><br /> <span class="token parameter">option</span> <span class="token operator">=></span> <span class="token function">append</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> option<span class="token punctuation">.</span>value<span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token function">append</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> el<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> result<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h2>Let's use <code>formDataMap()</code></h2>
<p>One great web platform feature with which to pair <code>formDataMap()</code> is event propagation, which enables us to capture events on the ancestor form:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>song-config<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span><br /> Song title:<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>text<span class="token punctuation">'</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>title<span class="token punctuation">'</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span><br /> Cowbell level: <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>cowbell<span class="token punctuation">'</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>range<span class="token punctuation">'</span></span> <span class="token attr-name">min</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>0<span class="token punctuation">'</span></span> <span class="token attr-name">max</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>11<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>submit<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>Apply configuration<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>form</span><span class="token punctuation">></span></span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>module<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br /> <span class="token keyword">const</span> form <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"song-config"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> form<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'input'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token function">formDataMap</span><span class="token punctuation">(</span>form<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">/* do great things with `data` */</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>Depending on the level of responsiveness the interactive demo needs, you can choose between <code>input</code>, <code>change</code> and <code>submit</code> events. The latter also gives you access to the form's <code>submitter</code> element, which can be factored into the returned data, a feature FormData does not currently support [<a href="https://github.com/whatwg/xhr/issues/262">whatwg/xhr#262</a>]:</p>
<pre class="language-js"><code class="language-js">form<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'submit'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token function">formDataMap</span><span class="token punctuation">(</span>form<span class="token punctuation">,</span> e<span class="token punctuation">.</span>submitter<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">/* do great things with `data` */</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><em>Update March 11, 2023:</em> The aforementioned issue has been fixed, with <code>submitter</code> added as a second, optional argument to the <code>FormData()</code> constructor. <a href="https://developer.chrome.com/en/blog/chrome-112-beta/">Chrome 112</a> is the first browser shipping support for it.</p>
<h2>Conclusion</h2>
<p>In all fairness, <small>[leans in and starts whispering:]</small> instead of reimplementing everything from scratch, you could get most of the same functionality with the form's vanilla <code>FormData</code> object <a href="https://vanillajstoolkit.com/helpers/serialize/">serialized to a plain JavaScript object</a>, followed by casting the values to numbers, dates, etc. as needed before using them for computations.</p>
<p>However, for that extra bit of convenience, <code>formDataMap()</code> is listed in full <a href="https://danburzo.ro/snippets/formdatamap/">on its separate page</a>.</p>
<hr />
<h2 id="handling-custom-elements">
Appendix: Handling custom HTML elements
</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals">ElementInternals</a> is a new API that lets custom HTML elements participate in forms. The article <a href="https://web.dev/more-capable-form-controls/">More capable form controls</a> by Arthur Evans goes into more detail, but in a nutshell, your custom element needs to:</p>
<ul>
<li>make itself form-associated by declaring the static <code>formAssociated</code> property;</li>
<li>access form functionality with the <code>ElementInternals.attachInternals()</code> method.</li>
</ul>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">MyControl</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span><br /> <span class="token keyword">static</span> formAssociated <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>internals <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachInternals</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// Set the element's submission value</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>internals<span class="token punctuation">.</span><span class="token function">setFormValue</span><span class="token punctuation">(</span><span class="token string">'some-value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br />customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">'my-control'</span><span class="token punctuation">,</span> MyControl<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>With the <code>setFormValue()</code> method, custom elements can specify their <em>submission value</em> which the <code>FormData</code> API can access. The submission value can be a string, a <code>Blob</code>, or a <code>FormData</code> object. The latter is used when the custom element wants to relay multiple values, and it's the only case when a custom element doesn't need a <code>name</code> attribute for it to be included in the form's submission data.</p>
<p>Unfortunately for us, the value set with the <code>setFormValue()</code> method is not accessible through any standard interface, so it's up to each custom element to decide how (and if) to expose an equivalent.</p>
<p>To remain generic, <code>formDataMap()</code> can only fall back to the standard <code>append(name, el.value)</code> approach.</p>
My favorite records from 20222022-12-05T00:00:00Zhttps://danburzo.ro/favorite-records-2022/<p>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).</p>
<ol>
<li><a href="https://aldousharding.bandcamp.com/album/warm-chris">Aldous Harding — Warm Chris</a></li>
<li>★ <a href="https://alkcm.bandcamp.com/album/oxy-music">Alex Cameron — Oxy Music</a></li>
<li><a href="https://angelolsen.bandcamp.com/album/big-time">Angel Olsen — Big Time</a></li>
<li><a href="https://annacalvi.bandcamp.com/album/tommy">Anna Calvi — Tommy</a></li>
<li><a href="https://www.arcadefire.com/">Arcade Fire — We</a></li>
<li><a href="https://arcticmonkeys.com/">Arctic Monkeys — The Car</a></li>
<li>★ <a href="https://www.deutschegrammophon.com/en/catalogue/products/solanales-balmorhea-12643">Balmorhea — Solanales</a></li>
<li><a href="https://beachhouse.bandcamp.com/album/once-twice-melody">Beach House — Once Twice Melody</a></li>
<li>★ <a href="https://bethorton.bandcamp.com/album/weather-alive">Beth Orton — Weather Alive</a></li>
<li><a href="https://biosphere.bandcamp.com/album/shortwave-memories">Biosphere — Shortwave Memories</a></li>
<li>★ <a href="https://bobbyoroza.bandcamp.com/album/get-on-the-otherside">Boby Oroza (feat. Cold Diamond & Mink) — Get on the Otherside</a></li>
<li><a href="https://www.brian-eno.net/">Brian Eno — <span class="sc">FOREVERANDEVERNOMORE</span></a></li>
<li><a href="https://carmband.bandcamp.com/album/carm-ii">CARM — CARM II</a></li>
<li><a href="https://danielavery.bandcamp.com/album/ultra-truth">Daniel Avery — Ultra Truth</a></li>
<li><a href="https://caribouband.bandcamp.com/album/cherry">Daphni — Cherry</a></li>
<li>☞ <a href="https://daniellanois.lnk.to/playerpiano">Daniel Lanois — Player, Piano</a></li>
<li><a href="https://florenceandthemachine.net/">Florence + the Machine — Dance Fever</a></li>
<li><a href="https://florist.bandcamp.com/album/florist">Florist — Florist</a></li>
<li><a href="https://haai.bandcamp.com/album/baby-we-re-ascending">HAAi — Baby, We're Ascending</a></li>
<li><a href="https://hagop.bandcamp.com/album/bolts">Hagop Tchaparian — Bolts</a></li>
<li><a href="https://jeanmicheljarre.com/">Jean-Michel Jarre — Oxymore</a></li>
<li><a href="https://www.deutschegrammophon.com/en/catalogue/products/drone-mass-johannsson-12620">Jóhann Jóhannsson, Theatre of Voices, Paul Hillier, <span class="sc">ACME</span> — Jóhannsson: Drone Mass</a></li>
<li><a href="https://kaitlynaureliasmith.bandcamp.com/album/i-could-be-your-dog-i-could-be-your-moon">Kaitlyn Aurelia Smith & Emile Mosseri — I Could Be Your Dog / I Could Be Your Moon</a></li>
<li>★ <a href="https://kalimalone.bandcamp.com/album/living-torch">Kali Malone — Living Torch</a></li>
<li><a href="https://kramer.bandcamp.com/album/music-for-films-edited-by-moths">Kramer — Music For Films Edited By Moths</a></li>
<li><a href="https://music.apple.com/us/album/sunset/1619436551">Library Tapes — Sunset</a></li>
<li>★ <a href="https://orcd.co/exaudia">Lisa Gerrard & Marcello De Francisci — Exaudia</a></li>
<li><a href="https://madrugada.no/">Madrugada — Chimes at Midnight</a></li>
<li>★ <a href="https://orcd.co/matthiasburden">Matthias Gusset — Burden</a></li>
<li><a href="https://metronomy.bandcamp.com/album/small-world">Metronomy — Small World</a></li>
<li><a href="https://nilsfrahm.bandcamp.com/album/music-for-animals">Nils Frahm — Music for Animals</a></li>
<li><a href="https://nosajthing.bandcamp.com/album/continua">Nosaj Thing — Continua</a></li>
<li><a href="https://orenambarchi.bandcamp.com/album/ghosted">Oren Ambarchi / Johan Berthling / Andreas Werliin — Ghosted</a></li>
<li><a href="https://perfumegenius.bandcamp.com/album/ugly-season">Perfume Genius — Ugly Season</a></li>
<li><a href="https://www.rogereno.com/">Roger Eno — The Turning Year</a></li>
<li><a href="https://motomami.rosalia.com/">Rosalía — Motomami</a></li>
<li>★ <a href="https://sarahdavachi.bandcamp.com/album/two-sisters">Sarah Davachi — Two Sisters</a></li>
<li><a href="https://sevdaliza.bandcamp.com/album/raving-dahlia">Sevdaliza — Raving Dahlia</a></li>
<li>★ <a href="https://sharonvanetten.bandcamp.com/album/weve-been-going-about-this-all-wrong-deluxe-edition">Sharon Van Etten — We've Been Going About This All Wrong (Deluxe Edition)</a></li>
<li><a href="https://sudanarchives.bandcamp.com/album/natural-brown-prom-queen">Sudan Archives — Natural Brown Prom Queen</a></li>
<li><a href="https://sunssignature.bandcamp.com/album/suns-signature">Sun's Signature — Sun's Signature</a></li>
<li><a href="https://thesmile.bandcamp.com/album/a-light-for-attracting-attention">The Smile — A Light for Attracting Attention</a></li>
<li><a href="https://tindersticks.bandcamp.com/album/stars-at-noon-original-soundtrack">Tindersticks — Stars at Noon (Original Soundtrack)</a></li>
<li><a href="https://vanessa-wagner.bandcamp.com/album/mirrored">Vanessa Wagner — Mirrored</a></li>
<li>★ <a href="https://vanessa-wagner.bandcamp.com/album/study-of-the-invisible">Vanessa Wagner — Study of the Invisible</a></li>
<li><a href="https://milan-records.myshopify.com/products/preorder-a-tribute-to-ryuichi-sakamoto-to-the-moon-and-back-2x-lp">Various Artists — A Tribute to Ryuichi Sakamoto: To the Moon and Back</a></li>
<li><a href="https://whomadewho.dk/releases">WhoMadeWho — <span class="sc">UUUU</span></a></li>
<li><a href="https://williambasinski.bandcamp.com/album/on-reflection">William Basinski & Janek Schaefer — “...On Reflection”</a></li>
<li><a href="https://yeahyeahyeahs.bandcamp.com/album/cool-it-down">Yeah Yeah Yeahs — Cool It Down</a></li>
</ol>
<p>The manicule ☞ marks retroactive additions of favorite albums I’ve discovered after having made the initial list.</p>
<p><strong>Live shows</strong> I've attended this year: Tindersticks in Lisbon; Dead Can Dance and Arctic Monkeys in Bucharest; Altın Gün and WhoMadeWho in Cluj.</p>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>Other 2022 lists: <a href="https://bleep.com/top-10-albums-of-the-year-2022">Bleep</a>, <a href="https://www.roughtrade.com/gb/collection/albums-of-the-year-2022">Rough Trade UK</a>, <a href="https://thequietus.com/articles/32400-the-quietus-top-100-albums-of-2022-norman-records">The Quietus</a>.</p>
Relational data in Eleventy2022-11-26T00:00:00Zhttps://danburzo.ro/eleventy-relational-data/<p>Many-to-many relationships are a fixture of structured content.</p>
<p>A basic example is a collection of <samp>Posts</samp>, each written by one or more <samp>Authors</samp>. So how do you shape the content so that it's easy to generate pages for <samp>Posts</samp> complete with nice bylines, and for individual <samp>Authors</samp> including a list of their posts?</p>
<h2>The setup</h2>
<p>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 <code>content/</code>, and use the Nunjucks templating language throughout. The corresponding <code>.eleventy.js</code> configuration is below:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* File: .eleventy.js */</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token parameter">config</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">markdownTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">htmlTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">dir</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">'content'</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>The <code>content/</code> folder holds all <samp>Posts</samp> and <samp>Authors</samp> in individual <code>.md</code> files. We'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.</p>
<p><samp>Posts</samp> reference each <samp>Author</samp> by their <em>file path relative to the input directory</em>. This path serves as an unique key for a piece of content. Here's how <code>content/posts/hello.md</code> might look:</p>
<pre class="language-md"><code class="language-md"><span class="token front-matter-block"><span class="token punctuation">---</span><br /><span class="token front-matter yaml language-yaml">title: Hello<br />authors:<br /> - authors/dan.md<br /> - authors/catalin.md<br />layout: layouts/post.njk</span><br /><span class="token punctuation">---</span></span><br /><br />This is our first collective post!</code></pre>
<p><samp>Author</samp> 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's <code>content/authors/dan.md</code>:</p>
<pre class="language-md"><code class="language-md"><span class="token front-matter-block"><span class="token punctuation">---</span><br /><span class="token front-matter yaml language-yaml">title: Dan Burzo<br />avatar: img/authors/dan-burzo.jpg<br />layout: layouts/author.html</span><br /><span class="token punctuation">---</span></span><br /><br />This is Dan's short bio.</code></pre>
<p>We want to accomplish two things:</p>
<ul>
<li>expand the <code>authors</code> array in the <samp>Post</samp>'s frontmatter into the actual pages they reference, so we can access all their properties in the template.</li>
<li>on the <samp>Author</samp> page, aggreggate the author's posts under the <code>author_posts</code> data field.</li>
</ul>
<h2>Augment the frontmatter with Computed Data</h2>
<p>Eleventy comes with a cool feature called <a href="https://www.11ty.dev/docs/data-computed/">Computed Data</a> that lets us add template data, at any level of the data cascade. More importantly, it lets us <em>overwrite</em> existing frontmatter data with richer values. We only need to define <code>eleventyComputed</code> once for each type of content, using <a href="https://www.11ty.dev/docs/data-template-dir/">directory data files</a>.</p>
<p>For <samp>Posts</samp>, the content of the <code>content/posts/posts.11tydata.js</code> file is shown below. By defining the <code>tags</code> field, we add all entries in the folder to the <code>posts</code> collection. We do the same in <code>content/authors/authors.11tydata.js</code> to gather all <samp>Authors</samp> in the corresponding collection.</p>
<blockquote>
<p>In Eleventy <a href="https://www.11ty.dev/docs/collections/">Collections</a>, the unique identifier for a piece of content is its <code>inputPath</code>, which is the items's file path relative to the project's root, starting with the path for the input directory. In our case, all <code>inputPath</code>s start with <code>./content/</code>.</p>
</blockquote>
<p>With <code>eleventyComputed</code>, we replace the <samp>Post</samp>'s <code>authors</code> field with the corresponding items from the <code>authors</code> collection, matching their <code>inputPath</code>. 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 <code>inputPath</code> values.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* content/posts/posts.11tydata.js */</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">layout</span><span class="token operator">:</span> <span class="token string">'layouts/post.njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token string">'posts'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">authors</span><span class="token operator">:</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> postAuthors <span class="token operator">=</span> data<span class="token punctuation">.</span>authors <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> collection <span class="token operator">=</span> data<span class="token punctuation">.</span>collections<span class="token punctuation">.</span>authors<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> postAuthors<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">authorPath</span> <span class="token operator">=></span> <br /> collection<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <br /> item<span class="token punctuation">.</span>inputPath <span class="token operator">===</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./content/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>authorPath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>The inverse, showing all <samp>Posts</samp> by a specific <samp>Author</samp>, is similarly done with <code>eleventyComputed</code>. This time, we look in the <code>posts</code> collection for items whose <code>authors</code> data field includes the current <code>inputPath</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* content/authors/authors.11tydata.js */</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">layout</span><span class="token operator">:</span> <span class="token string">'layouts/author.html'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token string">'authors'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">author_posts</span><span class="token operator">:</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> inputPath <span class="token operator">=</span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> data<span class="token punctuation">.</span>collections<span class="token punctuation">.</span>posts<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> postAuthors <span class="token operator">=</span> item<span class="token punctuation">.</span>data<span class="token punctuation">.</span>authors <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> postAuthors<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><br /> <span class="token parameter">authorPath</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./content/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>authorPath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token operator">===</span> inputPath<br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This setup makes it straightforward to use the enriched template data. In the layout for <samp>Posts</samp>...</p>
<pre class="language-twig"><code class="language-twig"><span class="token comment"><!-- content/_includes/layouts/post.njk --></span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>hgroup</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span> By<br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> author <span class="token operator">in</span> authors <span class="token delimiter punctuation">%}</span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> author<span class="token punctuation">.</span>url <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> author<span class="token punctuation">.</span>data<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%-</span> <span class="token tag-name keyword">if</span> <span class="token operator">not</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">-%}</span></span>, <span class="token twig language-twig"><span class="token delimiter punctuation">{%-</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">-%}</span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>hgroup</span><span class="token punctuation">></span></span><br /><br /><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> content <span class="token operator">|</span> safe <span class="token delimiter punctuation">}}</span></span></code></pre>
<p>...and for <samp>Authors</samp>:</p>
<pre class="language-twig"><code class="language-twig"><span class="token comment"><!-- content/_includes/layouts/author.html --></span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><br /><br /><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> content <span class="token operator">|</span> safe <span class="token delimiter punctuation">}}</span></span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> title <span class="token delimiter punctuation">}}</span></span>'s posts<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>figure</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ul</span><span class="token punctuation">></span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> post <span class="token operator">in</span> author_posts <span class="token delimiter punctuation">%}</span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>url <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>data<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ul</span><span class="token punctuation">></span></span></code></pre>
<p>The two basic patterns — <em>find by ID</em> and <em>filter by attribute</em> — can be replicated for each relationship between data types you care to represent.</p>
<p>But before we pepper that pesky <code>"./content/"</code> string around a dozen directory data files, let's factor it out, while improving performance in the process.</p>
<p>To make collection items easier to find by ID, let's put all the content in one big dictionary, indexed by the keys used across the <code>.md</code> files to reference each other: the file path relative to the input directory. In other words, the <code>inputPath</code> with the input directory trimmed. We do that by defining a <a href="https://www.11ty.dev/docs/collections/#advanced-custom-filtering-and-sorting">custom collection</a> called <code>_indexed</code> in the project's <code>.eleventy.js</code> config:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* .eleventy.js */</span><br /><br /><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token parameter">config</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /><br /> <span class="token keyword">const</span> <span class="token constant">INPUT_DIR</span> <span class="token operator">=</span> <span class="token string">'content'</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/*<br /> Index all content relative to the input directory<br /> in an Eleventy collection called `collections._indexed`<br /> */</span><br /> config<span class="token punctuation">.</span><span class="token function">addCollection</span><span class="token punctuation">(</span><span class="token string">'_indexed'</span><span class="token punctuation">,</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> index <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> data<span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> key <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">relative</span><span class="token punctuation">(</span><span class="token constant">INPUT_DIR</span><span class="token punctuation">,</span> item<span class="token punctuation">.</span>inputPath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> index<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> item<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> index<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">markdownTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">htmlTemplateEngine</span><span class="token operator">:</span> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">dir</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token constant">INPUT_DIR</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This custom collection simplifies our computed data. To fetch <samp>Post</samp> 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.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* content/posts/posts.11tydata.js */</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">layout</span><span class="token operator">:</span> <span class="token string">'layouts/post.njk'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token string">'posts'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">authors</span><span class="token operator">:</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> postAuthors <span class="token operator">=</span> data<span class="token punctuation">.</span>authors <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> index <span class="token operator">=</span> data<span class="token punctuation">.</span>collections<span class="token punctuation">.</span>_indexed<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> postAuthors<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">authorPath</span> <span class="token operator">=></span> index<span class="token punctuation">[</span>authorPath<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>In the case of <samp>Author</samp> posts, we replace string concatenation with reading the paths from the index, making the code if not faster then at least more resilient.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* content/authors/authors.11tydata.js */</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">layout</span><span class="token operator">:</span> <span class="token string">'layouts/author.html'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token string">'authors'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">author_posts</span><span class="token operator">:</span> <span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> inputPath <span class="token operator">=</span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> index <span class="token operator">=</span> data<span class="token punctuation">.</span>collections<span class="token punctuation">.</span>_indexed<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> data<span class="token punctuation">.</span>collections<span class="token punctuation">.</span>posts<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> postAuthors <span class="token operator">=</span> item<span class="token punctuation">.</span>data<span class="token punctuation">.</span>authors <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> postAuthors<br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">authorPath</span> <span class="token operator">=></span> index<span class="token punctuation">[</span>authorPath<span class="token punctuation">]</span><span class="token punctuation">.</span>inputPath<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>inputPath<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>These could be further refactored into reusable functions, to make declaring many relationships more palatable, but this is the basic idea.</p>
This website uses a variable font2022-08-22T00:00:00Zhttps://danburzo.ro/variable-fonts/<p>Around the end of 2021, this little corner of the web started using a variable font. <a href="https://www.productiontype.com/family/newsreader">Newsreader</a> is a beautiful design from Production Type commissioned by Google Fonts, whose sources are <a href="https://github.com/productiontype/NewsReader">available on GitHub</a> under the Open Font license (OFL).</p>
<p>I'd been reading about variable fonts for a while, but had <em>probably</em> 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.</p>
<h2>A first good thing to do is add all the necessary properties to the <code>@font-face</code> declaration</h2>
<p>Newsreader comes with two separate font files, one for roman and one for italics. Each file has two variation axes: the weight (<code>wght</code>) and the optical size (<code>opsz</code>).</p>
<p>My initial <code>@font-face</code> declaration for the roman style was pretty straightforward, and replacing the old typeface worked well out of the box.</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@font-face</span></span> <span class="token punctuation">{</span><br /> <span class="token property">font-family</span><span class="token punctuation">:</span> Newsreader<span class="token punctuation">;</span><br /> <span class="token property">font-style</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span><br /> <span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'/fonts/Newsreader.woff2'</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'woff2-variations'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">body</span> <span class="token punctuation">{</span><br /> <span class="token property">font-family</span><span class="token punctuation">:</span> Newsreader<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>It became apparent rather soon, however, that this setup only works in Firefox. Things that were supposed to look bold — headings, <code><strong></code>, and the like — didn't look as good in Safari and Chrome, as seen in <a href="https://danburzo.ro/demos/variable-fonts/declarations.html">this demo</a>. Both had trouble, in different ways, rendering <code>font-weight: 700</code>.</p>
<figure>
<a href="https://danburzo.ro/img/font-face-font-weight.png"><img src="https://danburzo.ro/img/font-face-font-weight.png" alt="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." width="406" height="211" /></a>
<figcaption>
<p>Each browser renders <code>font-weight: 700</code> differently.</p>
</figcaption>
</figure>
<p>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's <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-synthesis">font synthesis</a> with <code>font-synthesis: none</code> fixes the issue. As for Chrome, whatever it's doing, it's not improved by this declaration.</p>
<p>What gives? <code>wght</code> was supposed to be a registered variation axis that can be controlled via <code>font-weight</code> but, as seen in the demo, only <code>font-variant-settings: "wght" 700"</code> works consistently across browsers for bold text.</p>
<p>It was only obvious in retrospect that <strong>a webfont with a weight variation axis absolutely needs its weight range spelled out in the <code>@font-face</code> declaration</strong>, for the axis to be correctly mapped to the <code>font-weight</code> property. You do that with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight"><code>@font-face/font-weight</code></a> property:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@font-face</span></span> <span class="token punctuation">{</span><br /> <span class="token property">font-family</span><span class="token punctuation">:</span> Newsreader<span class="token punctuation">;</span><br /> <span class="token property">font-style</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span><br /> <span class="token property">font-weight</span><span class="token punctuation">:</span> 200 800<span class="token punctuation">;</span><br /> <span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'/fonts/Newsreader.woff2'</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'woff2-variations'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Similar declarations are needed for other registered axes:</p>
<ul>
<li>the <code>slnt</code> axis which controls the angle of the oblique style needs a range defined with <code>@font-face/font-style</code></li>
<li>the <code>wdth</code> axis which controls the font width needs a range defined with <code>@font-face/font-stretch</code></li>
</ul>
<p>Adding the correct <code>@font-face</code> properties enables declarations such as <code>font-style: oblique 30deg</code> or <code>font-width: 125%</code> to work as expected.</p>
<p>Variable fonts can also <em>technically</em> include the registered <code>ital</code> axis to bundle both roman and italic styles in a single file. Using such a font in CSS is <a href="https://rwt.io/typography-tips/getting-bent-current-state-italics-variable-font-support">a bit more complicated</a>, so as a consequence pretty much everyone bundles romans and italics in separate files.</p>
<h3>A note on <code>format()</code> in the <code>src</code> descriptor</h3>
<p>By the time I got to reading up on variable fonts, the syntax for <code>@font-face/src</code> <a href="https://drafts.csswg.org/css-fonts/#src-desc">in the spec</a> did no longer match the recommendations I'd found elsewhere for hinting to the browser that the <code>url()</code> points to a variable font.</p>
<p>Where were <code>format('woff2-variations')</code> and <code>format('woff2 supports variations')</code> coming from? But, more importantly, how to usefully hint variable fonts today? I made <a href="https://danburzo.ro/demos/variable-fonts/font-face-src.html">a browser test</a> to check support for the various syntaxes and came to these conclusions:</p>
<ul>
<li><code>format('*-variations')</code> was the proposed syntax around the time when browsers adopted variable fonts, and the only syntax supported by browsers at the time of writing;</li>
<li><code>format(woff2) tech(variations)</code> is the latest syntax. It separates the <em>font format</em> from <em>font technology descriptor</em>, as <a href="https://github.com/w3c/csswg-drafts/blob/main/css-fonts-4/src-explainer.md">explained</a> by Chris Lilley and Dominik Röttsches. This syntax is unsupported in current browsers and, if specified, must be done in a second <code>@font-face/src</code> declaration.</li>
</ul>
<p>With these findings in mind (which you can now find in the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#browser_compatibility">compatibility table on MDN</a>), variable fonts using the WOFF2 format only really need the <code>format('woff2-variations')</code> hint to ensure only browsers that have support for variations download the font file. This syntax works today and will most likely work indefinitely.</p>
<h2>It's also probably a good idea to optimize variable web fonts (as long as you're allowed to)</h2>
<p>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.</p>
<figure>
<img src="https://danburzo.ro/img/variable-fonts/newsreader-vf-axes.svg" width="340" />
<figcaption>Newsreader is based on nine master fonts, here distributed horizontally along the <em>Weight</em> axis (<code>wght</code>) and vertically along the <em>Optical size</em> (<code>opsz</code>) axis.</figcaption>
</figure>
<p>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. <a href="https://fonttools.readthedocs.io/en/latest/index.html"><code>fontTools</code></a> is a collection of libraries and command-line tools for all sorts of font manipulation written in Python.</p>
<p>Richard Rutter has a guide on installing <code>fonttools</code> on macOS and using it to <a href="https://clagnut.com/blog/2418/">subset variable fonts</a>. One understanding of <em>subsetting</em> 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 <em>split</em> 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 <a href="https://cloudfour.com/thinks/font-subsetting-strategies-content-based-vs-alphabetical/#implementing-alphabet-based-subsets">grab some ranges from Google Fonts</a> — 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.</p>
<h3>Narrowing the variation space</h3>
<p>In addition to glyph sets, there's another direction along which to subset a variable font. <code>fonttools</code> comes with <a href="https://fonttools.readthedocs.io/en/latest/varLib/instancer.html">the <code>varLib.instancer</code> module</a> that allows you <q>to create full instances (i.e. static fonts) from variable fonts, as well as 'partial' variable fonts that only contain a subset of the original variation space.</q></p>
<p>There is such a thing as <em>variable lite</em>. If, for example, you can make do with a single optical size, or you've decided <code>wght: 376</code> 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:</p>
<ul>
<li>produce a handful of static instances of the variable font, or</li>
<li>restrict the design-variation space to just the region you need.</li>
</ul>
<p>The command below pins the optical size at 12 while reducing the weight range to <code>[300–700]</code> for the Newsreader roman font:</p>
<pre class="language-bash"><code class="language-bash">fonttools varLib.instancer ./Newsreader.woff2 <span class="token assign-left variable">wght</span><span class="token operator">=</span><span class="token number">300</span>:700 <span class="token assign-left variable">opsz</span><span class="token operator">=</span><span class="token number">12</span> <span class="token parameter variable">-o</span> ./Newsreader-partial.woff2</code></pre>
<p>This cuts the file size in half, from 215kb to 98kb. I've intentionally picked a "bad" example, because the reduction is foremost attributable to removing the optical size variation axis. If we omit the <code>wght=300:700</code> option in our command we end up with mostly the same reduction (215kb to 102kb):</p>
<pre class="language-bash"><code class="language-bash">fonttools varLib.instancer ./Newsreader.woff2 <span class="token assign-left variable">opsz</span><span class="token operator">=</span><span class="token number">12</span> <span class="token parameter variable">-o</span> ./Newsreader-partial.woff2</code></pre>
<p>Recall that Newsreader uses font data from <code>wght=200,400,800</code>, corresponding to the minimum, default, and maximum weight. To render the <code>wght=300:700</code> 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 <code>wght=400:800</code>:</p>
<pre class="language-bash"><code class="language-bash">fonttools varLib.instancer ./Newsreader.woff2 <span class="token assign-left variable">wght</span><span class="token operator">=</span><span class="token number">400</span>:800 <span class="token assign-left variable">opsz</span><span class="token operator">=</span><span class="token number">12</span> <span class="token parameter variable">-o</span> ./Newsreader-partial.woff2</code></pre>
<h3>Legal aspects of subsetting</h3>
<p>Fonts are distributed under a variety of licenses that govern their use. The following apply to fonts distributed under the <a href="https://en.wikipedia.org/wiki/SIL_Open_Font_License">SIL Open Font License</a>, as is the case for Newsreader. Obviously, I'm not a lawyer so this is just my layperson understanding of the matter.</p>
<ul>
<li>Converting a <code>.ttf</code> / <code>.otf</code> file to a <code>.woff</code> / <code>.woff2</code> 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;</li>
<li>Subsetting a font alters its functionality, so it produces a derivative work, which is subject to some rules.</li>
</ul>
<p>When producing a derivative work you need to change the font's name to something completely different <em>when</em> and <em>if</em> the OFL license mentions a Reserved Font Name (RFN) as part of its copyright line. Newsreader has the following copyright line:</p>
<figure>
<blockquote>
<p>Copyright 2020 The Newsreader Project Authors (http://github.com/<wbr />productiontype/<wbr />Newsreader)</p>
</blockquote>
<figcaption>A sample copyright line from the OFL license that does not reserve the font name.</figcaption>
</figure>
<p>If the authors wanted <em>Newsreader</em> 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, <em>Oldswriter</em>.</p>
<figure>
<blockquote>
<p>Copyright 2020 The Newsreader Project Authors (http://github.com/<wbr />productiontype/<wbr />Newsreader), with Reserved Font Name Newsreader</p>
</blockquote>
<figcaption>A sample copyright line from the OFL license that reserves the font name.</figcaption>
</figure>
<p>Anyways, just something to be aware of. And if I'm getting this wrong, I would appreciate a nudge!</p>
<hr />
<h2>Addenda: readings, tools, and resources</h2>
<p>I can't help myself from turning every article into a little compendium, so here are some tangentials I've found useful.</p>
<p>OpenType Font Variations is part of the <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview">OpenType specification</a>. John Hudson announced this addition to OpenType 1.8 with <a href="https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369">Introducing OpenType Variable Fonts</a>, highly recommended reading.</p>
<p>A few guides to getting started:</p>
<ul>
<li>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide">Variable fonts guide</a> on MDN</li>
<li><a href="https://variablefonts.io/">A Variable Fonts Primer</a>. Still from Jason Pamental, <a href="https://rwt.io/typography-tips/variable-fonts-what-web-authors-need-know">Variable Fonts: What Authors Need to Know</a> and <a href="https://24ways.org/2019/an-introduction-to-variable-fonts/">An Introduction to Variable Fonts</a> for 24 Ways</li>
<li><a href="https://web.dev/variable-fonts/">Introduction to variable fonts on the web</a> on web.dev.</li>
<li>The newly-inaugurated <a href="https://fonts.google.com/knowledge">Google Fonts Knowledge</a> covers variable fonts.</li>
<li>Zach Leatherman on developing a <a href="https://www.zachleat.com/web/css-tricks-web-fonts/">strategy for loading fonts on CSS tricks</a>.</li>
</ul>
<p>An excellent tool for inspecting variable fonts (and, really, any kind of webfont) is Wakamai Fondue, available <a href="https://wakamaifondue.com/">on the web</a> and <a href="https://pixelambacht.nl/2021/wakamai-fondue-command-line/">at the command line</a>.</p>
<p>Finally, a couple of fun facts that surfaced while researching this article:</p>
<ul>
<li>Chris Coyier: <a href="https://chriscoyier.net/2022/08/02/actually-the-san-francisco-typeface-does-ship-as-a-variable-font/">Actually, the San Francisco Typeface Does Ship as a Variable Font</a>.</li>
<li>Firefox is the only browser that uses <code>b, strong { font-weight: bolder }</code> in its user agent stylesheet. Šime Vidas <a href="https://css-tricks.com/firefoxs-bolder-default-is-a-problem-for-variable-fonts/">points out</a> how this can be a problem with variable fonts.</li>
<li><a href="https://stackoverflow.com/questions/52203351/why-is-unicode-restricted-to-0x10ffff">the largest Unicode code point</a> is <code>U+10FFFF</code>.</li>
</ul>
Sizing images based on their aspect ratio2022-08-07T00:00:00Zhttps://danburzo.ro/aspect-ratio-size/<p>Grids of logos are my nightmare.</p>
<p>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.</p>
<p>Like with all tedious things, I've found myself wishing more than once that CSS included a declarative way to make images of various aspect ratios look the same size.</p>
<h2>Making images „look the same size”</h2>
<p>A definition of that might be to make the images occupy the <em>same surface area</em>. Let's work out the math.</p>
<p>We want to keep constant the area of an image of natural width <code>W</code> and height <code>H</code>. To that end, we must find its <em>preferred dimensions</em> <code>Wp</code> and <code>Hp</code> so that <code>Wp × Hp</code> is uniform across images. The givens:</p>
<pre class="language-js"><code class="language-js">area <span class="token operator">=</span> Wp <span class="token operator">*</span> Hp<span class="token punctuation">;</span><br />aspect_ratio <span class="token operator">=</span> <span class="token constant">W</span> <span class="token operator">/</span> <span class="token constant">H</span> <span class="token operator">=</span> Wp <span class="token operator">/</span> Hp<span class="token punctuation">;</span></code></pre>
<p>…ultimately lead to the formula below. (Individual steps are left as an exercise to the reader.)</p>
<pre class="language-js"><code class="language-js">Wp <span class="token operator">=</span> <span class="token function">sqrt</span><span class="token punctuation">(</span>area<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token constant">W</span> <span class="token operator">/</span> <span class="token constant">H</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Let's express this formula in HTML and CSS.</p>
<p>You should already have explicit <code>width</code> and <code>height</code> attributes on <code><img></code> elements in your markup to define the intrinsic size of the image and <a href="https://jakearchibald.com/2022/img-aspect-ratio/">prevent layout shifts</a> 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 <code>style</code> attribute.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>ratio-gallery<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <br /> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>https://source.unsplash.com/random/800x600<span class="token punctuation">'</span></span> <br /> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>800 by 600 pixels placeholder image<span class="token punctuation">'</span></span><br /> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>800<span class="token punctuation">'</span></span> <br /> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>600<span class="token punctuation">'</span></span> <br /> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token value css language-css"><span class="token property">--w</span><span class="token punctuation">:</span> 800<span class="token punctuation">;</span> <span class="token property">--h</span><span class="token punctuation">:</span> 600</span><span class="token punctuation">'</span></span></span><br /> <span class="token punctuation">/></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Note that we're passing the <code>width</code> and <code>height</code> as unitless numbers. This gives us the most flexibility when using the values in CSS math functions. The <a href="https://drafts.csswg.org/css-values-4/#exponent-funcs"><code>sqrt()</code></a> function, in particular, only accepts <code><number></code>s. Our basic CSS looks like this:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.ratio-gallery</span> <span class="token punctuation">{</span><br /> <span class="token property">--basis</span><span class="token punctuation">:</span> 300px<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.ratio-gallery img</span> <span class="token punctuation">{</span><br /> <span class="token property">--w</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br /> <span class="token property">--h</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--basis<span class="token punctuation">)</span> * <span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--w<span class="token punctuation">)</span> / <span class="token function">var</span><span class="token punctuation">(</span>--h<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Instead of defining an <code>--area</code> constant like in our math formula, we use its square root directly as <code>--basis</code>. 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, <code>--basis: 300px</code> makes the image as large as a <code>300×300</code> square.</p>
<p>That'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 <code>sqrt()</code> CSS function — which, at the moment of writing, is just Safari — you should see three images of identical surface areas:</p>
<figure>
<ul class="ratio-gallery">
<li>
<img src="https://danburzo.ro/img/aspect-ratio-size/500x500.png" alt="500 by 500 pixels placeholder image" width="500" height="500" style="--w: 500; --h: 500" />
</li>
<li>
<img src="https://danburzo.ro/img/aspect-ratio-size/800x400.png" alt="800 by 400 pixels placeholder image" width="800" height="400" style="--w: 800; --h: 400" />
</li>
<li>
<img src="https://danburzo.ro/img/aspect-ratio-size/400x600.png" alt="400 by 600 pixels placeholder image" width="400" height="600" style="--w: 400; --h: 600" />
</li>
</ul>
</figure>
<h2>Fallback to a fixed aspect ratio</h2>
<p>The following <code>@supports</code> at-rule will check if the browser supports the <code>sqrt()</code> CSS function:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/*<br /> To detect support for `sqrt()` we use `flex`,<br /> the shortest (afaict) CSS property that accepts <br /> the `<number>` value that `sqrt()` produces.<br /> */</span><br /><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">flex</span><span class="token punctuation">:</span> <span class="token function">sqrt</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /><br /><span class="token punctuation">}</span></code></pre>
<p>In browsers that don't support this function, we could enforce a fixed <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio"><code>aspect-ratio</code></a> and use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit"><code>object-fit</code></a> property to clip the image to fit, as <a href="https://css-irl.info/aspect-ratio-is-great/">described by Michelle Barker</a>. 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's the technique along with the fallback:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.ratio-gallery</span> <span class="token punctuation">{</span><br /> <span class="token property">--basis</span><span class="token punctuation">:</span> 300px<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.ratio-gallery img</span> <span class="token punctuation">{</span><br /> <span class="token property">--w</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br /> <span class="token property">--h</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--basis<span class="token punctuation">)</span> * <span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--w<span class="token punctuation">)</span> / <span class="token function">var</span><span class="token punctuation">(</span>--h<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">flex</span><span class="token punctuation">:</span> <span class="token function">sqrt</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">.ratio-gallery img</span> <span class="token punctuation">{</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--basis<span class="token punctuation">)</span> * 1.224 <span class="token comment">/* = sqrt(3/2) */</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 3/2<span class="token punctuation">;</span><br /> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Here is a more developed demo:</p>
<ul>
<li><a href="https://danburzo.ro/demos/image-sizes.html">Demo: equally-sized images, with fallback</a></li>
</ul>
<h2>Wrapping up</h2>
<p>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.</p>
<hr />
<h2>Addenda</h2>
<h3>Defensive CSS</h3>
<p>As Šime helpfully <a href="https://twitter.com/simevidas/status/1556423012336943105">points out</a>, the basic technique does not handle extreme aspect ratios well. In fact, the <em>arbitrary</em> in <em>arbitrary aspect ratio</em> is the cue to put our <a href="https://defensivecss.dev/">defensive CSS</a> hat on.</p>
<p>We can limit the space the image takes in any one direction with <code>max-width</code> and <code>max-height</code>:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.ratio-gallery img</span> <span class="token punctuation">{</span><br /> <span class="token property">--w</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br /> <span class="token property">--h</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--basis<span class="token punctuation">)</span> * <span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--w<span class="token punctuation">)</span> / <span class="token function">var</span><span class="token punctuation">(</span>--h<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span><br /> <span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--basis<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">max-height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--basis<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>(You might also want to throw in an <code>object-fit: cover</code> to handle very tall images that get squished when <code>max-height</code> kicks in.)</p>
<p>Here is the demo again, which includes the defensive CSS:</p>
<ul>
<li><a href="https://danburzo.ro/demos/image-sizes.html">Demo: equally-sized images, with fallback</a></li>
</ul>
<h3>Prior art</h3>
<p><strong>Update Dec. 2022</strong>: Piper Haywood had described back in 2020 <a href="https://piperhaywood.com/images-consistent-surface-area/">a technique for sizing images</a> using the <code>sqrt()</code> CSS function, at a time when the function was not yet supported in any browser. Nick Sherman produced a <a href="https://nicksherman.com/size-by-area/">nice demo</a> for the technique, using an approximation of <code>sqrt()</code>.</p>
Button type matters, after all2022-03-26T00:00:00Zhttps://danburzo.ro/button-type-button/<p>The <code><button type='button'></code> construct is <span class="sc">HTML</span>'s <em>eat your veggies</em>. It's good advice and you should always do it. Between you and me, we both know it's a bit silly: <em>of course a button is a button, I mean…</em></p>
<p>Well, <em>buddy</em>, if you've been omitting your <code>type</code> attribute with impunity, I'm here to tell you that one day, without warning, this habit is going to bite you in the ass. I know it because <em>*cue pensive piano music*</em> 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.</p>
<p>In all honesty, the actual event lacked the gravitas of my dramatization here, but I thought it was delightfully unintuitive form behavior so <a href="https://twitter.com/danburzo/status/1507418069219651587">I did a twete</a>. Seeing other people go <em>huh</em>, I thought I'd unpack the idea a bit.</p>
<p>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:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token punctuation">'</span></span> <span class="token special-attr"><span class="token attr-name">onsubmit</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token value javascript language-javascript"><span class="token function">login</span><span class="token punctuation">(</span>…<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span><span class="token punctuation">'</span></span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span>Username: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>text<span class="token punctuation">'</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>username<span class="token punctuation">'</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span>Password: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>password<span class="token punctuation">'</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>password<span class="token punctuation">'</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>submit<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>Log in<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>form</span><span class="token punctuation">></span></span></code></pre>
<figure class="form-figure">
<form action="https://danburzo.ro/button-type-button/" onsubmit="alert("logging you in"); return false;">
<p>
<label>Username: <input type="text" name="username" /></label>
</p>
<p>
<label>Password: <input type="password" name="password" /></label>
</p>
<button type="submit">Log in</button>
</form>
</figure>
<p>Now let's say you add to the form a button that reveals the password in plain text:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token punctuation">'</span></span> <span class="token special-attr"><span class="token attr-name">onsubmit</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token value javascript language-javascript"><span class="token function">login</span><span class="token punctuation">(</span>…<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span><span class="token punctuation">'</span></span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span>Username: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>text<span class="token punctuation">'</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>username<span class="token punctuation">'</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span>Password: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>password<span class="token punctuation">'</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>password<span class="token punctuation">'</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token value javascript language-javascript"><span class="token keyword">this</span><span class="token punctuation">.</span>form<span class="token punctuation">.</span>password<span class="token punctuation">.</span>type<span class="token operator">=</span><span class="token string">"text"</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span><span class="token punctuation">'</span></span></span><span class="token punctuation">></span></span><br /> Show password<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>submit<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>Log in<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>form</span><span class="token punctuation">></span></span></code></pre>
<figure class="form-figure">
<form action="https://danburzo.ro/button-type-button/" onsubmit="alert("logging you in"); return false;">
<p>
<label>Username: <input type="text" name="username" /></label>
</p>
<p>
<label>Password: <input type="password" name="password" /></label>
<button onclick="this.form.password.type="text"; return false;">Show password</button>
</p>
<button type="submit">Log in</button>
</form>
</figure>
<p>What unexpected behavior have you introduced to this form with the addition of that button?</p>
<hr />
<p><strong>Answer:</strong> If the user presses the <kbd>Enter</kbd> key after they've typed in their password, instead of submitting the form, the user's password is revealed in plain text.</p>
<p>The <em>Show password</em> button lacks an explicit <code>type='button'</code>and so it gets the default <code>type='submit'</code>. Outside of forms, this has no ill effect: without a form to submit, the button behaves like <code>type='button'</code> in that it does nothing. Put it into a form and it suddenly behaves <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type">as an actual submit button</a>.</p>
<p>When the user presses the <kbd>Enter</kbd> key while focused on one of the form's inputs, the form gets submitted via the so-called <em>implicit submission</em>. The browser doesn't simply submit the form, it <strong>actually clicks</strong> the first submit button it finds in the form. From the <a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission">HTML spec</a> (emphasis mine):</p>
<blockquote>
<p>If the user agent supports letting the user submit a form implicitly (for example, on some platforms hitting the "enter" key while a text control is focused implicitly submits the form), then doing so for a form, whose default button <strong>[ie. the first submit button in tree order whose form owner is that form element]</strong> has activation behavior and is not disabled, <strong>must cause the user agent to fire a <code>click</code> event at that default button</strong>.</p>
</blockquote>
<p>Because the <em>Show password</em> 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's password. Ta-daa!</p>
<p>And <em>that's why</em> you always write <code><button type='button'></code>.</p>
<h2>Coda: actually marking up a login form</h2>
<p>Besides the point I was trying to make about implicit form submission, there are more things that this toy markup misses, among which:</p>
<ul>
<li>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete"><code>autocomplete</code> attribute</a> can help password managers make better choices about storing and filling in information for the form.</li>
<li>Users of assistive technology are not well served by the <em>Show password</em> functionality. If you're serious about implementing this feature, Nicolas Steenhout touches on the topic in his <a href="https://incl.ca/show-hide-password-accessibility-and-password-hints-tutorial/">Show/Hide password accessibility and password hints tutorial</a>, and the GDS document even more gotchas in <a href="https://technology.blog.gov.uk/2021/04/19/simple-things-are-complicated-making-a-show-password-option/">Simple things are complicated: making a show password option</a> (<a href="https://twitter.com/philw_/status/1507683185584394240"><span class="sc">h/t</span></a> Phil). But, more importantly, everyone <a href="https://github.com/whatwg/html/issues/7293">seems to agree</a> that we would be better off if browsers themselves had the feature baked in.</li>
</ul>
Observe an element's focus-within state2022-03-18T19:23:37Zhttps://danburzo.ro/focus-within/<p>With this short tip, we'll devise a way to tell when a DOM element's <em>focus-within</em> state changes, that is observing whenever the element, or any of its descendants, holds the focus.</p>
<p>You may already be familiar with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within">the CSS <code>:focus-within</code> pseudo-class</a> that provides such a hook:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/*<br /> Emphasize the container when it has focus within.<br /> */</span><br /><span class="token selector">.container:focus-within</span> <span class="token punctuation">{</span><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px dotted currentColor<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>In JavaScript, you could <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/matches">match that CSS pseudo-class</a> or look at the containment of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement">the active element</a> to check if, at that particular point in time, the focus is within a DOM element:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> Test if the container has focus within with either:<br /><br /> * matching the :focus-within CSS pseudo-class<br /> * checking that the active element is contained within<br /> */</span><br />containerElement<span class="token punctuation">.</span><span class="token function">matches</span><span class="token punctuation">(</span><span class="token string">':focus-within'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />containerElement<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span>activeElement<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Those checks are great for one-off tests, but what if we want to observe <em>changes</em> in the element's focus containment? While there are no specific DOM events for it, we'll build our own using the available focus events.</p>
<h2>DOM focus events: a refresher</h2>
<p>There are four events under the <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent"><code>FocusEvent</code></a> umbrella that tell us when an object obtains or loses focus. Here they are, helpfully categorized like bottled water:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event"><code>focusin</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event"><code>focusout</code></a> are the <em>bubbly events</em>;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event"><code>focus</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event"><code>blur</code></a> are the <em>still events</em> (they don't, ahem, bubble).</li>
</ul>
<p>I don't have the exact reasons for why the <code>focus</code> and <code>blur</code> events don't bubble up the DOM tree, but Peter-Paul Koch has <a href="https://www.quirksmode.org/dom/events/blurfocus.html">a plausible explanation</a>:</p>
<blockquote>
<p>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.</p>
</blockquote>
<p>The great thing about all focus events, which makes many scenarios much simpler to manage, is that their <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/relatedTarget"><code>relatedTarget</code> property</a> points to the <em>other relevant element</em>. For events related to loss of focus (<code>blur</code> and <code>focusout</code>) the related target is the element that will receive the focus next, if any; for events signifying acquisition of focus (<code>focus</code> and <code>focusin</code>), the related target is the previously focused element, if any.</p>
<p>Here's the sequence of events that gets triggered for when focus shifts from element <span class="el-marker el-marker--a">A</span> to element <span class="el-marker el-marker--b">B</span>:</p>
<table>
<thead>
<tr>
<th>Order</th>
<th>Event</th>
<th><code>.target</code></th>
<th><code>.relatedTarget</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><code>blur</code></td>
<td><span class="el-marker el-marker--a">A</span></td>
<td><span class="el-marker el-marker--b">B</span></td>
</tr>
<tr>
<td>2</td>
<td><code>focusout</code></td>
<td><span class="el-marker el-marker--a">A</span></td>
<td><span class="el-marker el-marker--b">B</span></td>
</tr>
<tr>
<td>3</td>
<td><code>focus</code></td>
<td><span class="el-marker el-marker--b">B</span></td>
<td><span class="el-marker el-marker--a">A</span></td>
</tr>
<tr>
<td>4</td>
<td><code>focusin</code></td>
<td><span class="el-marker el-marker--b">B</span></td>
<td><span class="el-marker el-marker--a">A</span></td>
</tr>
</tbody>
</table>
<p>All this being said, let's write some code.</p>
<h2>Observing the focus-within state</h2>
<p>Whenever the container, or one of its descendants, <em>receives</em> focus, the <code>focusin</code> event bubbles up the DOM tree to the container (and beyond). The event's <code>relatedTarget</code> property points to the previously focused element, if any. To detect when the container <em>first</em> receives focus from outside, we can check for containment:</p>
<pre class="language-js"><code class="language-js">containerElement<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'focusin'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>currentTarget<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>relatedTarget<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Focus was already in the container */</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Focus was received from outside the container */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>We use the same check for containment to detect when focus <em>leaves</em> the element, this time with the <code>focusout</code> event. Since this event's <code>relatedTarget</code> property now points to where the focus is heading next, the check works the other way around too:</p>
<pre class="language-js"><code class="language-js">containerElement<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'focusout'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>currentTarget<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>relatedTarget<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Focus will still be within the container */</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Focus will leave the container */</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And that's it! Within a few short lines, we're already handling focus containment changes that cover a whole range of input methods (mouse, touch, keyboard).</p>
<h2>Handling focus outside the page</h2>
<p>When used as the trigger for hiding <a href="https://adrianroselli.com/2021/07/stop-using-pop-up.html"><em>pop-ups</em></a> or <a href="https://adrianroselli.com/2020/03/stop-using-drop-down.html"><em>drop-downs</em></a>, the basic <code>focusout</code> implementation can occasionally be too eager: annoyingly, the element disappears before we can inspect it with the browser's developer tools.</p>
<p>This is just one instance of the focus moving <em>outside</em> the web page (without necessarily hiding the page). In these cases we might decide we want to keep the element visible.</p>
<p>In the code sample below, we use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus">the <code>Document.hasFocus()</code> method</a> to detect the lost focus and keep the element visible:</p>
<pre class="language-js"><code class="language-js">containerElement<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'focusout'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> If the document has lost focus,<br /> skip the containment check <br /> and keep the element visible.<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">hasFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>e<span class="token punctuation">.</span>currentTarget<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>relatedTarget<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">hideSelf</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>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 <em>just sits in our face</em> when we get back and poke around the page. It doesn't know we're back.</p>
<p>Before giving away the document's focus, we need to attach a listener for when, if ever, the focus returns. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event">window <code>focus</code> event</a> — the non-bubbly kind — is perfect here, when paired with one of the two on-demand tests we mentioned in the introduction:</p>
<pre class="language-js"><code class="language-js">containerElement<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'focusout'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> self <span class="token operator">=</span> e<span class="token punctuation">.</span>currentTarget<span class="token punctuation">;</span><br /> <span class="token comment">/*<br /> If the document has lost focus,<br /> don't hide the container just yet,<br /> wait until the focus is returned.<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">hasFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'focus'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token function">focusReturn</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* <br /> We want the listener to be triggered just once,<br /> so we have it remove itself from the `focus` event.<br /> */</span><br /> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'focus'</span><span class="token punctuation">,</span> focusReturn<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/*<br /> Test whether the container is still in the DOM <br /> and whether the active element is contained within.<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>self<span class="token punctuation">.</span>isConnected <span class="token operator">&&</span> <span class="token operator">!</span>self<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span>activeElement<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">hideSelf</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>self<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>relatedTarget<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">hideSelf</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>A couple of notes on this listener:</p>
<ul>
<li>Because the <code>focus</code> event that returns focus to the page can happen after a long time (or maybe never), it's useful to check that the element <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected">is still in the DOM</a> when it finally occurs.</li>
<li>Since the event listener is set up to remove itself after the first invocation, we don't expect it to generate a memory leak. The <code>addEventListener()</code> method does have the convenient <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters"><code>{ once: true }</code></a> option but the do-it-yourself way is more resilient. A browser that doesn't support the <code>once</code> option would treat the function as a normal listener and keep it around <em>forever</em> — leaks ahoy!</li>
</ul>
<h2>Fixing some browser quirks</h2>
<h3>Blurring in iOS Safari</h3>
<p>In iOS Safari, tapping on non-focusable elements on the page won'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 <em>tap outside to blur</em> functionality works as expected, we can mark the body as focusable with <code>tabindex='-1'</code>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>en<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>-1<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><br /> …<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span></code></pre>
<h2>Conclusion</h2>
<p>You can see the full code in action in the demo below, which compares the CSS <code>:focus-within</code> pseudo-class with the JavaScript implementation:</p>
<ul>
<li><a href="https://danburzo.ro/demos/focus-within.html">Demo: Detect focus-within state</a></li>
</ul>
My favorite records from 20212022-01-02T00:00:00Zhttps://danburzo.ro/favorite-records-2021/<img loading="lazy" src="https://danburzo.ro/img/top-20-albums-2021.jpg" alt="The covers of my top 20 albums of 2021 arranged in a grid" width="1492" height="1200" />
<p>Here are my top 20 albums of 2021, in alphabetical order:</p>
<ol>
<li><a href="https://awvfts.bandcamp.com/album/invisible-cities"><strong>A Winged Victory for the Sullen — Invisible Cities</strong></a></li>
<li><a href="https://akirarabelais.bandcamp.com/album/a-la-recherche-du-temps-perdu"><strong>Akira Rabelais — À La Recherche Du Temps Perdu</strong></a></li>
<li><a href="https://www.deutschegrammophon.com/en/catalogue/products/the-wind-balmorhea-12219"><strong>Balmorhea — The Wind</strong></a></li>
<li><a href="https://biosphere.bandcamp.com/album/angels-flight"><strong>Biosphere — Angel's Flight</strong></a></li>
<li><a href="https://dustinohalloran.bandcamp.com/album/silfur"><strong>Dustin O'Halloran — Silfur</strong></a></li>
<li>★ <a href="https://elmichelsaffair.bandcamp.com/album/yeti-season"><strong>El Michels Affair — Yeti Season</strong></a></li>
<li><a href="https://www.youtube.com/playlist?list=PL0r5wXgga2jLlt9y1OR66rhziHs-VjLYw"><strong>Enfant Sauvage — Petrichor</strong></a></li>
<li><a href="https://music.apple.com/gb/album/1548025973"><strong>Jay-Jay Johanson — Rorschach Test</strong></a></li>
<li><a href="https://josgonzlez.bandcamp.com/album/local-valley"><strong>José González — Local Valley</strong></a></li>
<li><a href="https://indigoraw.bandcamp.com/album/microscope-of-heraclitus-reworked"><strong>Kazuya Nagaya — Microscope of Heraclitus Reworked</strong></a></li>
<li><a href="https://music.apple.com/gb/album/fragments-ep/1575379050"><strong>Keaton Henson — Fragments</strong></a></li>
<li><a href="https://meitei.bandcamp.com/album/kofu-ii-ii"><strong>Meitei — Kofū II</strong></a></li>
<li><a href="https://mogwai.bandcamp.com/album/as-the-love-continues"><strong>Mogwai — As the Love Continues</strong></a></li>
<li><a href="https://heavenlyrecordings.bandcamp.com/album/ive-been-trying-to-tell-you"><strong>Saint Etienne — I've Been Trying To Tell You</strong></a></li>
<li><a href="https://music.apple.com/gb/album/save-me-not/1554077598"><strong>Sebastian Plano — Save Me Not</strong></a></li>
<li><a href="https://slowmeadow.bandcamp.com/album/upstream-dream"><strong>Slow Meadow — Upstream Dream</strong></a></li>
<li><a href="https://sofiakourtesis.bandcamp.com/album/fresia-magdalena"><strong>Sofia Kourtesis — Fresia Magdalena</strong></a></li>
<li><a href="https://theweatherstation.bandcamp.com/album/ignorance"><strong>The Weather Station — Ignorance</strong></a></li>
<li><a href="https://coldcut.bandcamp.com/album/0"><strong>Various Artists — @0</strong></a></li>
<li><a href="https://www.youtube.com/watch?v=BDwAlto-NKU"><strong>WhoMadeWho — Live at Abu Simbel, Egypt</strong></a></li>
</ol>
<p>More great albums released this year:</p>
<ol>
<li><a href="https://alexsomers.bandcamp.com/album/siblings">Alex Somers — Siblings</a> and <a href="https://alexsomers.bandcamp.com/album/siblings-2">Siblings 2</a></li>
<li><a href="https://altingun.bandcamp.com/album/yol">Altın Gün — Yol</a></li>
<li><a href="https://music.apple.com/ro/album/1588387718">Alva Noto — HYbr:ID I</a></li>
<li><a href="https://soundcloud.com/tikitarec/sets/tikita012-1">Amandra & Karim — Sqala</a></li>
<li><a href="https://music.apple.com/gb/album/never-the-right-time/1557478238">Andy Stott — Never the Right Time</a></li>
<li><a href="https://angelolsen.bandcamp.com/album/aisles">Angel Olsen — Aisles</a></li>
<li><a href="https://arushijain.bandcamp.com/album/under-the-lilac-sky">Arushi Jain — Under the Lilac Sky</a></li>
<li><a href="https://bicep.bandcamp.com/album/isles">Bicep — Isles</a></li>
<li><a href="https://bigredmachine.bandcamp.com/album/how-long-do-you-think-its-gonna-last">Big Red Machine — How Long Do You Think It's Gonna Last?</a></li>
<li><a href="https://blanckmass.bandcamp.com/album/in-ferneaux">Blanck Mass — In Ferneaux</a></li>
<li><a href="https://blawan.bandcamp.com/album/woke-up-right-handed">Blawan — Woke Up Right Handed</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLsh4QmACcJ6dDPPizqq3Fp6L1WiU6Zrnv">Bleachers — Take the Sadness Out of Saturday Night</a></li>
<li><a href="https://burial.bandcamp.com/album/shock-power-of-love-ep">Burial + Blackdown — Shock Power of Love</a></li>
<li><a href="https://burial.bandcamp.com/album/chemz-dolphinz">Burial — Chemz / Dolphinz</a></li>
<li><a href="https://cassandrajenkins.bandcamp.com/album/an-overview-on-phenomenal-nature">Cassandra Jenkins — An Overview on Phenomenal Nature</a></li>
<li><a href="https://caterinabarbieri.bandcamp.com/track/knot-of-spirit">Caterina Barbieri & Lyra Pramuk — Knot of Spirit</a></li>
<li><a href="https://colleencolleen.bandcamp.com/album/the-tunnel-and-the-clearing">Colleen — The Tunnel and the Clearing</a></li>
<li><a href="https://danielavery.bandcamp.com/album/together-in-static">Daniel Avery — Together in Static</a></li>
<li><a href="https://darkside.bandcamp.com/album/spiral"><span class="sc">DARKSIDE</span> — Spiral</a></li>
<li><a href="https://davidwaltersplay.bandcamp.com/album/nocturne">David Walters — Nocturne</a></li>
<li><a href="https://deafheavens.bandcamp.com/album/infinite-granite">Deafheaven — Infinite Granite</a></li>
<li><a href="https://devendrabanhart.bandcamp.com/album/refuge">Devendra Banhart & Noah Georgeson — Refuge</a></li>
<li><a href="https://umorrex.bandcamp.com/album/spume-recollection">Driftmachine — Spume & Recollection</a></li>
<li><a href="https://elmichelsaffair.bandcamp.com/album/the-abominable-ep">El Michels Affair — The Abominable EP</a></li>
<li><a href="https://thou.bandcamp.com/album/the-helm-of-sorrow">Emma Ruth Rundle & Thou — The Helm of Sorrow</a></li>
<li><a href="https://fabriziopaterlini.bandcamp.com/album/lifeblood">Fabrizio Paterlini — LifeBlood</a></li>
<li><a href="https://flyinglotus.bandcamp.com/album/yasuke">Flying Lotus — Yasuke</a></li>
<li><a href="https://kompakt-gas.bandcamp.com/album/der-lange-marsch"><span class="sc">GAS</span> — Der Lange Marsch</a></li>
<li><a href="https://godford.bandcamp.com/album/i-you-she">Godford — <span class="sc">I YOU SHE</span></a></li>
<li><a href="https://godspeedyoublackemperor.bandcamp.com/album/g-d-s-pee-at-state-s-end">Godspeed You! Black Emperor — G_d's Pee <span class="sc">AT STATE'S END!</span></a></li>
<li><a href="https://grouper.bandcamp.com/album/shade/">Grouper — Shade</a></li>
<li><a href="https://howdoesitfeeltobeloved.bandcamp.com/album/the-hill-the-light-the-ghost">Haiku Salut — The Hill, The Light, The Ghost</a></li>
<li><a href="https://shop.hammockmusic.com/album/elsewhere">Hammock — Elsewhere</a></li>
<li><a href="https://haniarani.bandcamp.com/album/music-for-film-and-theatre">Hania Rani — Music for Film and Theatre</a></li>
<li><a href="https://boomkat.com/products/dissent-1a961ac2-0108-4372-a21f-6c56affd7899">Heinrich Köbberling, Laurel Halo, Moritz Von Oswald Trio — Dissent</a></li>
<li><a href="https://heladonegro.bandcamp.com/album/far-in">Helado Negro — Far In</a></li>
<li><a href="https://music.apple.com/gb/album/1557713010">Jean-Michel Jarre — Amazônia</a></li>
<li><a href="https://jonhopkins.bandcamp.com/album/music-for-psychedelic-therapy">Jon Hopkins — Music for Psychedelic Therapy</a> and <a href="https://music.apple.com/ro/album/piano-versions-ep/1557647242">Piano Versions</a></li>
<li><a href="https://johannjohannsson.bandcamp.com/album/gold-dust">Jóhann Jóhannsson — Gold Dust</a></li>
<li><a href="https://jonsi.bandcamp.com/album/obsidian">Jónsi — Obsidian</a></li>
<li><a href="https://kangdingray.bandcamp.com/album/branches">kangding ray — Branches</a></li>
<li><a href="https://music.apple.com/gb/album/chemtrails-over-the-country-club/1545567745">Lana Del Rey — Chemtrails Over the Country Club</a> and <a href="https://music.apple.com/ro/album/blue-banisters/1584675130">Blue Banisters</a></li>
<li><a href="https://kmru.bandcamp.com/album/logue-2"><span class="sc">KMRU</span> — Logue</a></li>
<li><a href="https://theliminanas.bandcamp.com/album/de-pel-cula">Laurent Garnier & The Limiñanas — De Película</a></li>
<li><a href="https://librarytapes.bandcamp.com/album/dusk">Library Tapes — Dusk</a></li>
<li><a href="https://music.apple.com/gb/album/burn/1549003458">Lisa Gerrard & Jules Maxwell — Burn</a></li>
<li><a href="https://falseidols.bandcamp.com/album/lonely-guest">Lonely Guest — Lonely Guest</a></li>
<li><a href="https://music.apple.com/ro/album/1548726031">Marianne Faithfull — She Walks in Beauty (with Warren Ellis)</a></li>
<li><a href="https://store.deutschegrammophon.com/p51-i0028948604463/max-richter/exiles-2lp-/index.html">Max Richter, Kristjan Järvi, Baltic Sea Philharmonic — Exiles</a></li>
<li><a href="https://murcofmusic.bandcamp.com/album/the-alias-sessions">Murcof — The Alias Sessions</a></li>
<li><a href="https://music.apple.com/gb/album/carnage/1552929712">Nick Cave & Warren Ellis — <span class="sc">CARNAGE</span></a></li>
<li><a href="https://nightmaresonwax.bandcamp.com/album/shout-out-to-freedom">Nightmares On Wax — Shout Out! To Freedom...</a></li>
<li><a href="https://nilsfrahm.bandcamp.com/album/old-friends-new-friends">Nils Frahm — Old Friends New Friends</a></li>
<li><a href="https://www.youtube.com/playlist?list=OLAK5uy_kuVSVULMo3GB_kgsN7r96_AsUcYktsQ4Y">Ólafur Arnalds — When We Are Born</a></li>
<li><a href="https://perfumegenius.bandcamp.com/album/immediately-remixes">Perfume Genius — <span class="sc">IMMEDIATELY</span> Remixes</a></li>
<li><a href="https://perilazone.bandcamp.com/album/how-much-time-it-is-between-you-and-me">Perila — How Much Time it is Between You and Me?</a></li>
<li><a href="https://kida-mnesia.com/">Radiohead — <span class="sc">KID A MNESIA</span></a></li>
<li><a href="https://ryuichisakamotoanddavidtoop.bandcamp.com/album/garden-of-shadows-and-light">Ryuichi Sakamoto & David Toop — Garden of Shadows and Light</a></li>
<li><a href="https://sarahdavachi.bandcamp.com/album/antiphonals">Sarah Davachi — Antiphonals</a></li>
<li><a href="https://slowmeadow.bandcamp.com/album/happy-occident-reworks">Slow Meadow — Happy Occident Reworks</a></li>
<li><a href="https://music.sufjan.com/album/a-beginners-mind">Sufjan Stevens & Angelo De Augustine — A Beginner's Mind</a></li>
<li><a href="https://music.sufjan.com/album/convocations">Sufjan Stevens — Convocations</a></li>
<li><a href="https://sunn.bandcamp.com/album/metta-benevolence-bbc-6music-live-on-the-invitation-of-mary-anne-hobbs">Sunn O))) — Metta, Benevolence</a></li>
<li><a href="https://duststoredigital.com/album/music-for-photographers">The Black Dog — Music For Photographers</a></li>
<li><a href="https://music.apple.com/us/album/love-signs/1568231755">The Jungle Giants — Love Signs</a></li>
<li><a href="https://music.apple.com/gb/album/1576851901">The War on Drugs — I Don’t Live Here Anymore</a></li>
<li><a href="https://tindersticks.bandcamp.com/album/distractions">Tindersticks — Distractions</a></li>
<li><a href="https://v-twin.bandcamp.com/album/ookii-gekkou">Vanishing Twin — Ookii Gekkou</a></li>
<li><a href="https://music.apple.com/gb/album/welfare-jazz/1533668629">Viagra Boys — Welfare Jazz</a></li>
<li><a href="https://vladislavdelay.bandcamp.com/album/rakka-ii">Vladislav Delay — Rakka II</a></li>
<li><a href="https://xiuxiu.bandcamp.com/album/oh-no">Xiu Xiu — <span class="sc">OH NO</span></a></li>
</ol>
<p>Found after the year was over:</p>
<ul>
<li><a href="https://enzofavata.bandcamp.com/album/enzo-favata-the-crossing-2">Enzo Favata The Crossing</a> (found 2023)</li>
</ul>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>Other 2021 lists: <a href="https://www.roughtrade.com/gb/albums-of-the-year-2021">Rough Trade UK</a>, <a href="https://www.roughtrade.com/us/albums-of-the-year-2021-e9439d65-16e2-410d-9176-f4fa18db01d7">Rough Trade US</a>, <a href="https://bleep.com/top-10-albums-of-the-year-2021">Bleep</a>, <a href="https://thequietus.com/articles/30903-the-quietus-top-100-albums-of-2021-norman-records">The Quietus</a>, <a href="https://boomkat.com/charts/boomkat-end-of-year-charts-2021">Boomkat</a>, <a href="https://www.thewire.co.uk/audio/tracks/the-wire-s-releases-of-the-year-2021">The Wire</a>.</p>
Pinch me, I'm zooming: gestures in the DOM2021-11-07T00:00:00Zhttps://danburzo.ro/dom-gestures/<p>Interpreting multi-touch user gestures on the web is not as straightforward as you'd imagine.</p>
<p>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 <code>wheel</code>, <code>touch</code>, and <code>gesture</code> DOM events.</p>
<details>
<summary>Table of contents</summary>
<ul>
<li><a href="https://danburzo.ro/dom-gestures/#gesture-anatomy">Anatomy of a gesture</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#dom-events">The relevant DOM events: wheel, touch, and gesture</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#browser-tests">Putting browsers to the test</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#unify-wheel-touch-gesture">Unifying wheel, touch, and gesture events</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#apply-transform">Applying the transformation</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#further-refinements">Refining the implementation</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#conclusion">Conclusion</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#questions-answers">Questions & Answers</a></li>
<li><a href="https://danburzo.ro/dom-gestures/#revisions">Revisions</a></li>
</ul>
</details>
<h2 id="gesture-anatomy">Anatomy of a gesture</h2>
<p>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 <a href="https://math.stackexchange.com/questions/716147/find-2d-affine-transform-matrix-given-a-pair-of-points">combination of translation, uniform scaling, and rotation</a> to apply to the target element. This is also known as an (affine) linear transformation.</p>
<p>The impression of <a href="https://en.wikipedia.org/wiki/Direct_manipulation_interface">direct manipulation</a> is created when the transformation <a href="https://material.io/design/interaction/gestures.html">maps naturally</a> to the movement of the touchpoints. There are ideas for <a href="http://dx.doi.org/10.1145/2642918.2647352">novel ways to interpret a gesture</a>, 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're most familiar with, and the one we will explore in this article.</p>
<p>Let's see how a two-finger gesture maps to the basic components of a linear transformation.</p>
<img src="https://danburzo.ro/img/pinch-zoom/scale.png" alt="Two touchpoints brought together, a solid line marking the distance between them." width="384" height="384" />
<p><strong>Scale.</strong> 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.</p>
<img src="https://danburzo.ro/img/pinch-zoom/rotation.png" alt="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." width="384" height="384" />
<p><strong>Rotation.</strong> The slope defined by the two touchpoints dictates the rotation to be applied to the object.</p>
<img src="https://danburzo.ro/img/pinch-zoom/translation.png" alt="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." width="384" height="384" />
<p><strong>Origin and translation.</strong> The <em>midpoint</em>, 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.</p>
<p>Native applications on touch-enabled devices have to access to <a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures">high-level APIs</a> 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.</p>
<h2 id="dom-events">The relevant DOM events: wheel, touch, and gesture</h2>
<p>A <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent"><code>WheelEvent</code></a> is triggered when the user intends to scroll an element with the mousewheel (from which the interface takes its name), a separate "scroll area" on older trackpads, or the entire surface area of newer trackpads with the two-finger vertical movement.</p>
<p>Wheel events have <code>deltaX</code>, <code>deltaY</code>, and <code>deltaZ</code> properties to encode the displacement dictated by the input device, and a <code>deltaMode</code> to establish the unit of measurement:</p>
<table>
<thead>
<tr>
<th><code>deltaMode</code></th>
<th>Value</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>DOM_DELTA_PIXEL</code></td>
<td><code>0</code></td>
<td>scroll a precise amount of pixels</td>
</tr>
<tr>
<td><code>DOM_DELTA_LINE</code></td>
<td><code>1</code></td>
<td>scroll by lines</td>
</tr>
<tr>
<td><code>DOM_DELTA_PAGE</code></td>
<td><code>2</code></td>
<td>scroll by entire pages</td>
</tr>
</tbody>
</table>
<p>As pinch gestures on trackpads became more commonplace, browser implementers needed a way support them in desktop browsers. Kenneth Auchenberg, in his <a href="https://medium.com/@auchenberg/detecting-multi-touch-trackpad-gestures-in-javascript-a2505babb10e">article on detecting multi-touch trackpad gestures</a>, brings together key pieces of the story.</p>
<p>In short, Chrome settled on <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=289887">an approach</a> inspired by Internet Explorer: encode pinch gestures as <code>wheel</code> events with <code>ctrlKey: true</code>, and the <code>deltaY</code> property holding the proposed scale increment. Firefox eventually <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1052253">did the same</a>, and with Microsoft Edge recently having switched to Chromium as its underlying engine, we have a "standard" of sorts. I use scare-quotes because, as we'll see in the next section, some aspects don't quite match across browsers.</p>
<p>Sometime between Chrome and Firefox adding support for pinch-zoom, <a href="https://webkit.org/blog/6008/new-web-features-in-safari/">Safari 9.1</a> brought <a href="https://developer.apple.com/documentation/webkitjs/gestureevent">its very own <code>GestureEvent</code></a>, which exposes precomputed <code>scale</code> and <code>rotation</code> properties, to the desktop.</p>
<p>To this day, Safari remains the only browser that implements <code>GestureEvent</code>, even among browsers on touch-enabled platforms. Instead, mobile browsers produce the lower-level <a href="https://developer.mozilla.org/en-US/docs/Web/API/Touch_events"><code>TouchEvent</code>s</a>, 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.</p>
<p>Whereas <code>WheelEvent</code> only encodes scale, and <code>GestureEvent</code> includes scale and rotation, <code>TouchEvent</code> lets us capture the translation as well, giving us finer-grained control over how we interpret the gesture.</p>
<p>Combined <code>wheel</code>, <code>gesture</code> and <code>touch</code> events seem sufficient to handle two-finger gestures across a variety of platforms. Let's see how this intuition, *ahem*, pans out.</p>
<h2 id="browser-tests">Putting browsers to the test</h2>
<p>I've put together a test page that logs relevant properties of all the wheel, gesture, and touch events it captures:</p>
<p><a href="https://danburzo.ro/demos/dom-gestures/logger.html"><strong>DOM Gesture Logger</strong></a></p>
<p>I'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:</p>
<ul>
<li>a MacBook Pro (macOS Big Sur);</li>
<li>a Surface Laptop with a touchscreen and built-in <a href="https://www.howtogeek.com/286905/what-is-a-precision-touchpad-on-windows-pcs/">precision touchpad</a> (Windows 10);</li>
<li>an ASUS notebook with a non-precision touchpad (Windows 10);</li>
<li>an iPhone (iOS 14);</li>
<li>an iPad with a keyboard (iPadOS 14); and</li>
<li>an external mouse to connect to all laptops.</li>
</ul>
<p>Let's dig into a few of the results, and how they inform our solution.</p>
<h3>Results on macOS</h3>
<p>When you perfrom a pinch-zoom gesture, Firefox and Chrome produce a <code>wheel</code> event with a <code>deltaY: ±scale, ctrlKey: true</code>. They produce an identical result when you scroll normally with two fingers while physically pressing down the <kbd>Ctrl</kbd> key, with the difference that the latter is subject to inertial scrolling.</p>
<p>Safari reacts to the proprietary <code>gesturestart</code>, <code>gesturechange</code>, and <code>gestureend</code> events and produces a precomputed <code>scale</code> and <code>rotation</code>.</p>
<p>In all browsers, <code>clientX</code> and <code>clientY</code>, as well as the position of the on-screen cursor, remain constant throughout two-finger gestures. This pair of coordinates establishes the gesture origin.</p>
<p id="wheel-behavior">
In the process of testing various modifier keys, I noted some default browser behaviors that you'll likely need to deflect with <code>event.preventDefault()</code>:
</p>
<ul>
<li><kbd>Option + wheel</kbd> in Firefox navigates (or rather <em>flies</em>) 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.</li>
<li><kbd>Command + wheel</kbd> in Firefox zooms in and out of the page, similarly to the <kbd>Command +</kbd> and <kbd>Command -</kbd> keyboard shortcuts;</li>
<li>Pinching inwards in Safari minimizes the tab into a tab overview screen.</li>
</ul>
<p>External, third-party mice are a different matter. Instead of the smooth pixel increments on the trackpad, the mouse's wheel jumps entire <em>lines</em> at a time. (The <samp>Scrolling speed</samp> setting in <samp>System Preferences > Mouse</samp> controls how many.)</p>
<p>Accordingly, Firefox shows <code>deltaY: ±1, deltaMode: DOM_DELTA_LINE</code> for a tick of the wheel. This is the first, and at least on macOS the only, encounter with <code>DOM_DELTA_LINE</code>. Chrome and Safari stick with <code>deltaMode: DOM_DELTA_PIXEL</code> and a much larger <code>deltaY</code>, sometimes hundreds of pixels at a time. This is an instance of the <em>many more pixels than expected</em> deviation of which we'll see more throughout the test session. A basic pinch-zoom implementation that doesn't account for this quirk will zoom in and out in large, hard-to-control strides when you use the mousewheel.</p>
<p>In all three browsers, <code>deltaX</code> is normally zero. Holding down the <kbd>Shift</kbd> key, a common way for users of an external mouse to scroll horizontally, swaps deltas and <code>deltaY</code> becomes zero instead.</p>
<h3>Results on Windows</h3>
<p>A precision touchpad works on Windows similarly to the Magic Trackpad on macOS: Firefox, Chrome, and Edge produce results comparable to what we've seen on macOS. The quirks emerge with non-precision touchpads and external mice, however.</p>
<p>On Windows, the wheel of an external mouse has two scroll modes: either <code>L</code> lines at a time (with a configurable <code>L</code>), or a whole <em>page</em> at a time.</p>
<p>When you use the external mouse with line-scrolling, Firefox produces the expected <code>deltaY: ±L, deltaMode: DOM_DELTA_LINE</code>. Chrome generates <code>deltaY: ±L * N, deltaMode: DOM_DELTA_PIXEL</code>, where <code>N</code> is a multiplier dictated by the browser, and which varies by machine: I've seen <code>33px</code> on the ASUS laptop and <code>50px</code> on the Surface. (There's probably an inner logic to what's going on, but it doesn't warrant further investigation at this point.) Edge produces <code>deltaY: ±100, deltaMode: DOM_DELTA_PIXEL</code>, so <code>100px</code> regardless on the number of lines <code>L</code> that the mouse is configured to scroll. With page-scrolling, browsers uniformly report <code>deltaY: ±1, deltaMode: DOM_DELTA_PAGE</code>. None of the three browsers support holding down the <kbd>Shift</kbd> to reverse the scroll axis of the mousewheel.</p>
<p>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.</p>
<p>In Firefox, in line-scrolling mode, scrolls on both axes produce <code>deltaMode: DOM_DELTA_LINE</code> with <code>deltaX</code> and <code>deltaY</code>, respectively, containing a fraction of a line; a pinch gesture produces a constant <code>deltaY: ±L, deltaMode: DOM_DELTA_LINE, ctrlKey: true</code>. In page-scrolling mode, scrolls on the primary axis produce <code>deltaMode: DOM_DELTA_PAGE</code>, while on the secondary axis it remains in <code>deltaMode: DOM_DELTA_LINE</code>; the pinch gesture produces <code>deltaY: ±1, deltaMode: DOM_DELTA_PAGE, ctrlKey: true</code>. In Chrome, a surprising result is that when you scroll on the secondary axis you get <code>deltaX: 0, deltaY: N * ±L, shiftKey: true</code>. Otherwise, the effects seen with a non-precision touchpad on Windows are of the <em>unexpected <code>deltaMode</code></em> or <em>unexpected <code>deltaY</code> value</em> varieties.</p>
<h2 id="unify-wheel-touch-gesture">Unifying wheel, touch, and gesture events</h2>
<p>The API that unifies wheel, touch, and gesture events should look a lot like Safari's <code>GestureEvent</code>. We will model it around three callbacks:</p>
<ul>
<li><code>startGesture(gesture)</code>, invoked at the beginning of the transformation;</li>
<li><code>doGesture(gesture)</code>, invoked continously throughout the transformation;</li>
<li><code>endGesture(gesture)</code>, invoked at the end of the transformation.</li>
</ul>
<p>The <code>gesture</code> object should describe the transformation completely:</p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> …<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> … <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> …<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> … <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> …<span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> …<br /><span class="token punctuation">}</span></code></pre>
<p>Let's see how each type of event maps to this setup.</p>
<h3 id="handle-gesture-events">Processing Safari's gesture events</h3>
<p><code>GestureEvent</code>s are the most straightforward to map, so we'll start with those.</p>
<p>We pick <code>scale</code> and <code>rotation</code> properties directly from the event object, and fill in the <code>origin</code> based on the mouse coordinates. Since these types of events don't include a translation, we assume there to be <em>no translation</em>, that is <code>translation: { x: 0, y: 0 }</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/* Handle Safari GestureEvents */</span><br /><br /><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br />container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'gesturestart'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> e<span class="token punctuation">.</span>scale<span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> e<span class="token punctuation">.</span>rotation<span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">passive</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'gesturechange'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">doGesture</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> e<span class="token punctuation">.</span>scale<span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> e<span class="token punctuation">.</span>rotation<span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">passive</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'gestureend'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">endGesture</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> e<span class="token punctuation">.</span>scale<span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> e<span class="token punctuation">.</span>rotation<span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>An occasional misfire where the <code>gestureend</code> event is emitted twice at the end of a gesture (<a href="https://bugs.webkit.org/show_bug.cgi?id=233137">WebKit#233137</a>) is worked around with the <code>gesture</code> flag.</p>
<p>Safari also has a built-in <em>pinch to show all tabs</em> gesture which normally gets canceled when we call <code>preventDefault()</code> on the gesture events. However, when the user combines a scale and rotation gesture, the browser behavior will be triggered nonetheless (<a href="https://bugs.webkit.org/show_bug.cgi?id=233141">WebKit#233141</a>). I don't have a workaround yet, but would love to know if there is one.</p>
<details>
<summary>Sidenote: Passive event listeners</summary>
<p>It used to be the case that we could <code>preventDefault()</code> 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) <a href="https://dom.spec.whatwg.org/#observing-event-listeners">leads to less than ideal performance</a>.</p>
<p>To mitigate this, the <code>passive</code> flag was introduced as an option for <code>addEventListener()</code>. To be able to prevent the default browser behavior, you'd need to register an <em>active</em> event listener by sending in the <code>{ passive: false }</code> option.</p>
<p>For backwards compatibility, events are active by default. But, in some browsers for some performance-critical contexts, <a href="https://www.chromestatus.com/feature/6662647093133312">they default to being passive</a>. To make sure our event listeners work as expected (now, and going forwaRD), throughout the code samples we use <code>{ passive: false }</code> explicitly whenever we want to call <code>preventDefault()</code>, first checking that the event is <code>cancelable</code>.</p>
</details>
<p>Starting with Safari 15, macOS Safari also emits <code>wheel</code> events for the pinch-zoom gesture (<a href="https://bugs.webkit.org/show_bug.cgi?id=225788">WebKit#225788</a>). If you're only interested in scale transformations, in the future you'll be able listen to <code>wheel</code> events, as shown in the next section, instead of relying on the non-standard <code>GestureEvent</code>.</p>
<h3>Converting wheel events to gestures</h3>
<p>For wheel events, we have a few things to figure out:</p>
<ol>
<li>How to normalize the various ways browsers emit <code>wheel</code> events into an uniform delta value.</li>
<li>How to map the occurrence of <code>wheel</code> events to <code>startGesture</code>, <code>doGesture</code>, and <code>endGesture</code>.</li>
<li>How to compute the <code>scale</code> value from the delta.</li>
</ol>
<p>Let's explore each sub-problem one by one.</p>
<h4>1. Normalizing <code>wheel</code> events</h4>
<p>Our goal here is to implement a <code>normalizeWheelEvent</code> function as described below:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> Normalizes WheelEvent `e`,<br /> returning an array of deltas `[dx, dy]`.<br />*/</span><br /><span class="token keyword">function</span> <span class="token function">normalizeWheelEvent</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> dx <span class="token operator">=</span> e<span class="token punctuation">.</span>deltaX<span class="token punctuation">;</span><br /> <span class="token keyword">let</span> dy <span class="token operator">=</span> e<span class="token punctuation">.</span>deltaY<span class="token punctuation">;</span><br /> <span class="token comment">// TODO: normalize dx, dy</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span>dx<span class="token punctuation">,</span> dy<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is where we can put our experimental browser data to good use. Let's recap some findings relevant to normalizing <code>wheel</code> events.</p>
<p>The browser may emit <code>deltaX: 0, deltaY: N, shiftKey: true</code> when scrolling horizontally. We want to interpret this as <code>deltaX: N, deltaY: 0</code> instead:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// swap `dx` and `dy`:</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>dx <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">&&</span> e<span class="token punctuation">.</span>shiftKey<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span>dx<span class="token punctuation">,</span> dy<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>dy<span class="token punctuation">,</span> dx<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Furthermore, the browser may emit values in a <code>deltaMode</code> other than pixels; for each, we need a multiplier:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>deltaMode <span class="token operator">===</span> WheelEvent<span class="token punctuation">.</span><span class="token constant">DOM_DELTA_LINE</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> dx <span class="token operator">*=</span> <span class="token number">8</span><span class="token punctuation">;</span><br /> dy <span class="token operator">*=</span> <span class="token number">8</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>deltaMode <span class="token operator">===</span> WheelEvent<span class="token punctuation">.</span><span class="token constant">DOM_DELTA_PAGE</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> dx <span class="token operator">*=</span> <span class="token number">24</span><span class="token punctuation">;</span><br /> dy <span class="token operator">*=</span> <span class="token number">24</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The choice of multipliers ultimately depends on the application. We might take inspiration <a href="https://stackoverflow.com/questions/20110224/what-is-the-height-of-a-line-in-a-wheel-event-deltamode-dom-delta-line">from browsers themselves</a> 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.</p>
<p>Finally, a browser may not emit <code>DOM_DELTA_LINE</code> or <code>DOM_DELTA_PAGE</code> where the input device settings dictate them, and instead offer a premultiplied value in <code>DOM_DELTA_PIXEL</code>s. This value is often very large, <code>100px</code> or more at a time.</p>
<p>Why would browsers do that? With a whole lot of code out there that fails to look at the <code>deltaMode</code>, minuscule <code>DOM_DELTA_LINE</code> / <code>DOM_DELTA_PAGE</code> 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 <code>wheel</code> events as signifying scroll intents — makes them harder to use for other purposes.</p>
<p>Thankfully, even in the absence of a more sophisticated approach, simply setting the upper limit of <code>deltaY</code> to something reasonable (e.g. <code>24px</code>), just to pull the breaks on wild zooms, can go a long way towards improving the experience:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> Note: we're using `Math.sign()` and `Math.min()` <br /> to impose a maximum on the *absolute* value <br /> of a possibly-negative number.<br /> */</span><br />dy <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span>dy<span class="token punctuation">)</span> <span class="token operator">*</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">24</span><span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>dy<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>These few adjustments should cover a vast array of variations across browsers and devices. Yay compromise!</p>
<h4>2. Generating gesture events from <code>wheel</code> events</h4>
<p>With normalization out of the way, the next obstacle is that <code>wheel</code> events are individual occurrences, for which we must devise a "start" and "end" if we want to call <code>startGesture</code> and <code>endGesture</code>.</p>
<p>The first <code>wheel</code> 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 <code>wheel</code> event. An outline for batching wheel events into gestures is listed below:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> timer<span class="token punctuation">;</span><br /><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br />container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'wheel'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">/*<br /> If we don't have a `gesture`, it means<br /> we're just starting a batch of `wheel` events.<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token comment">/*<br /> Perform the current wheel gesture<br /> */</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span> … <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token function">doGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>timer<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span><span class="token function">clearTimeout</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token comment">/*<br /> When the time runs out, call `endGesture`.<br /> */</span><br /> timer <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">endGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// timeout in milliseconds</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">passive</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The actual <code>gesture</code> arguments to <code>startGesture</code>, <code>doGesture</code>, and <code>endGesture</code> functions are explored in the next section.</p>
<h4>3. Converting the wheel delta to a <code>scale</code></h4>
<p>In Safari, a <code>gesturechange</code> event's <code>scale</code> property holds the <em>accumulated</em> scale to apply to the object at each moment of the gesture:</p>
<pre class="language-js"><code class="language-js">final_scale <span class="token operator">=</span> initial_scale <span class="token operator">*</span> event<span class="token punctuation">.</span>scale<span class="token punctuation">;</span></code></pre>
<p>In fact, <a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures/handling_pinch_gestures">the documentation for the <code>UIPinchGestureRecognizer</code></a> which native iOS apps use to detect pinch gestures, and which works similarly to Safari's <code>GestureEvent</code>, emphasizes this aspect:</p>
<blockquote>
<p><strong>Important</strong>: 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.</p>
</blockquote>
<p>On the other hand, pinch gestures encoded as <code>wheel</code> events contain deltas that correspond to <em>percentual changes</em> in scale that you're supposed to apply incrementally. Negative deltas increase the object's scale, while positive deltas dicrease the object's scale. For example:</p>
<ul>
<li><code>delta: 10</code> shrinks the object by <code>10%</code></li>
<li><code>delta: -20</code> enlarges the object by <code>20%</code></li>
</ul>
<p>A single formula covers both cases:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> scale <span class="token operator">=</span> previous_scale <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> delta <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Accumulating a series of deltas <code>d1</code>, <code>d2</code>, …, <code>dN</code> into a final scaling factor requires some back-of-the-napkin arithmetics. The intermediary scales…</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> scale1 <span class="token operator">=</span> initial_scale <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> d1<span class="token operator">/</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> scale2 <span class="token operator">=</span> scale1 <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> d2<span class="token operator">/</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> scale3 <span class="token operator">=</span> scale2 <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> d3<span class="token operator">/</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// …and so on</span></code></pre>
<p>…lead us to the formula for the final scale:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> factor <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> d1<span class="token operator">/</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> d2<span class="token operator">/</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">*</span> … <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> dN<span class="token operator">/</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> final_scale <span class="token operator">=</span> initial_scale <span class="token operator">*</span> factor<span class="token punctuation">;</span></code></pre>
<p>This lets us flesh out the <code>scale</code> we're supposed to send to our <code>startGestue</code>, <code>doGesture</code> and <code>endGesture</code> functions we introduced in the previous section:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> timer<span class="token punctuation">;</span><br /><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br />container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'wheel'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">let</span> <span class="token punctuation">[</span>dx<span class="token punctuation">,</span> dy<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">normalizeWheel</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>ctrlKey<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// pinch-zoom gesture</span><br /> <span class="token keyword">let</span> factor <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> dy <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>scale <span class="token operator">*</span> factor<span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>translation<br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token comment">// pan gesture</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>scale<span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <br /> <span class="token literal-property property">x</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>x <span class="token operator">-</span> dx<span class="token punctuation">,</span> <br /> <span class="token literal-property property">y</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>y <span class="token operator">-</span> dy<br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">doGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>timer<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span><span class="token function">clearTimeout</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> timer <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">endGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">passive</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This approach will get us <code>scale</code> values in the same ballpark for <code>WheelEvent</code> and <code>GestureEvent</code>, but you'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 <code>SPEEDUP</code> multiplier that makes up for the difference:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> Eyeballing it suggests the sweet spot<br /> for SPEEDUP is somewhere between <br /> 1.5 and 3. Season to taste!<br />*/</span><br /><span class="token keyword">const</span> <span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">=</span> <span class="token number">2.5</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> factor <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">*</span> dy<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>As Stephane points out over email, there's one final touch-up we could make that involves the symmetry of negative and positive <code>dy</code> values: when you shrink an object by <code>20%</code>, you can't bring it back to its original scale by enlarging it by <code>20%</code>.</p>
<p>That wheel events for pinching in and out don't add up to zero is an unfortunate side-effect of how browsers <a href="https://hg.mozilla.org/mozilla-central/file/b81970e39db444fa9a70eaf2e656f3e48c18a7c1/widget/InputData.cpp#l622">have implemented</a> the pinch-to-wheel feature. To work around the asymmetry we can adjust how we <q>shrink the object by N%</q> to mean <q>shrink it by X% so that enlarging it by N% would restore the original scale</q>. Solving the equation for X:</p>
<pre><code>(1 - X/100) * (1 + N/100) = 1
</code></pre>
<p>…gives us the adjusted factor for positive values of <code>dy</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> factor <span class="token operator">=</span> dy <span class="token operator"><=</span> <span class="token number">0</span> <span class="token operator">?</span> <br /> <span class="token number">1</span> <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">*</span> dy<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span> <span class="token operator">:</span> <br /> <span class="token number">1</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">*</span> dy<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The listing below contains the final code for handling wheel events, let's see what we can do about touch events next.</p>
<details>
<summary>Full listing for the wheel handling code</summary>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token constant">WHEEL_TRANSLATION_SPEEDUP</span> <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> timer<span class="token punctuation">;</span><br /><br />container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'wheel'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">let</span> <span class="token punctuation">[</span>dx<span class="token punctuation">,</span> dy<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">normalizeWheel</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>ctrlKey<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// pinch-zoom</span><br /> <span class="token keyword">let</span> factor <span class="token operator">=</span> dy <span class="token operator"><=</span> <span class="token number">0</span> <span class="token operator">?</span> <br /> <span class="token number">1</span> <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">*</span> dy<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span> <span class="token operator">:</span> <br /> <span class="token number">1</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token constant">WHEEL_SCALE_SPEEDUP</span> <span class="token operator">*</span> dy<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>scale <span class="token operator">*</span> factor<span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>translation<br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token comment">// panning</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>clientY <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>scale<span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <br /> <span class="token literal-property property">x</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>x <span class="token operator">-</span> <span class="token constant">WHEEL_TRANSLATION_SPEEDUP</span> <span class="token operator">*</span> dx<span class="token punctuation">,</span> <br /> <span class="token literal-property property">y</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>y <span class="token operator">-</span> <span class="token constant">WHEEL_TRANSLATION_SPEEDUP</span> <span class="token operator">*</span> dy<br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">doGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>timer<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span><span class="token function">clearTimeout</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> timer <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">endGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">passive</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</details>
<h3>Converting touch events to gestures</h3>
<p>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 <code>event.touches</code> list as a <code>Touch</code> object containing, among others, its coordinates <code>clientX</code> and <code>clientY</code>.</p>
<h4>Emitting gesture-like events</h4>
<p>The four touch events are <code>touchstart</code>, <code>touchmove</code>, <code>touchend</code> and <code>touchcancel</code>.
We want to map these to the <code>startGesture</code>, <code>doGesture</code> and <code>endGesture</code> callbacks.</p>
<p>Each individual touch produces a <code>touchstart</code> event on contact and a <code>touchend</code> event when lifted from the touchscreen. The <code>touchcancel</code> event is emitted when the browser wants to bail out of the gesture (for example, when adding too many touchpoints to the screen).</p>
<p>For our purpose we want to observe gestures involving exactly two touchpoints, and we use the same function <code>watchTouches</code> for all three events.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">watchTouches</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> gesture <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span>…<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchmove'</span><span class="token punctuation">,</span> touchMove<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchend'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchcancel'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> gesture <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token function">endGesture</span><span class="token punctuation">(</span>…<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'touchmove'</span><span class="token punctuation">,</span> touchMove<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'touchend'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'touchcancel'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br />document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchstart'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The <code>touchmove</code> event is the only one using its own separate listener:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">touchMove</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">doGesture</span><span class="token punctuation">(</span>…<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>cancelable <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h4>Producing the linear transformation</h4>
<p>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 <code>TouchList</code> and <code>Touch</code> objects are immutable and just save a reference:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> initial_touches<span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">watchTouches</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> gesture <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> initial_touches <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">;</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span>…<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> …<br /> <span class="token punctuation">}</span><br /> …<br /><span class="token punctuation">}</span></code></pre>
<p>The argument to <code>startGesture</code> is straightforward: we haven'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:</p>
<pre class="language-js"><code class="language-js"><span class="token function">startGesture</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token function">midpoint</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>With the midpoint computed as:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">midpoint</span><span class="token punctuation">(</span><span class="token parameter">touches</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> <span class="token punctuation">[</span>t1<span class="token punctuation">,</span> t2<span class="token punctuation">]</span> <span class="token operator">=</span> touches<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token punctuation">(</span>t1<span class="token punctuation">.</span>clientX <span class="token operator">+</span> t2<span class="token punctuation">.</span>clientX<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">,</span> <br /> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token punctuation">(</span>t1<span class="token punctuation">.</span>clientY <span class="token operator">+</span> t2<span class="token punctuation">.</span>clientY<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>For the <code>doGesture</code> 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):</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">distance</span><span class="token punctuation">(</span><span class="token parameter">touches</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> <span class="token punctuation">[</span>t1<span class="token punctuation">,</span> t2<span class="token punctuation">]</span> <span class="token operator">=</span> touches<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">hypot</span><span class="token punctuation">(</span>t2<span class="token punctuation">.</span>clientX <span class="token operator">-</span> t1<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> t2<span class="token punctuation">.</span>clientY <span class="token operator">-</span> t1<span class="token punctuation">.</span>clientY<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">angle</span><span class="token punctuation">(</span><span class="token parameter">touches</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> <span class="token punctuation">[</span>t1<span class="token punctuation">,</span> t2<span class="token punctuation">]</span> <span class="token operator">=</span> touches<span class="token punctuation">;</span><br /> <span class="token keyword">let</span> dx <span class="token operator">=</span> t2<span class="token punctuation">.</span>clientX <span class="token operator">-</span> t1<span class="token punctuation">.</span>clientX<span class="token punctuation">;</span><br /> <span class="token keyword">let</span> dy <span class="token operator">=</span> t2<span class="token punctuation">.</span>clientY <span class="token operator">-</span> t1<span class="token punctuation">.</span>clientY<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">atan2</span><span class="token punctuation">(</span>dy<span class="token punctuation">,</span> dx<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">180</span> <span class="token operator">/</span> Math<span class="token punctuation">.</span><span class="token constant">PI</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We can produce the argument to <code>doGesture</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> mp_init <span class="token operator">=</span> <span class="token function">midpoint</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> mp_curr <span class="token operator">=</span> <span class="token function">midpoint</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token function">doGesture</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token function">distance</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token function">distance</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> <span class="token function">angle</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">angle</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">x</span><span class="token operator">:</span> mp_curr<span class="token punctuation">.</span>x <span class="token operator">-</span> mp_init<span class="token punctuation">.</span>x<span class="token punctuation">,</span> <br /> <span class="token literal-property property">y</span><span class="token operator">:</span> mp_curr<span class="token punctuation">.</span>y <span class="token operator">-</span> mp_init<span class="token punctuation">.</span>y<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> mp_init<br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Finally, let's tackle the argument to <code>endGesture</code>. It can't be computed on the spot, since at the moment when <code>endGesture</code> gets called, we explicitly <em>don't</em> have two touchpoints available anymore. We must keep track of the last gesture we have produced and send that to the <code>endGesture</code> function. The <code>gesture</code> variable, which up until this point held a boolean value, sounds like a good place to do it.</p>
<p>Expand the section below to see how the <code>watchTouches</code> and <code>touchMove</code> functions look with everything put together.</p>
<details>
<summary>Full listing for the touch events code</summary>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> initial_touches<span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">watchTouches</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> initial_touches <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> <span class="token function">midpoint</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'touchstart'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">startGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchmove'</span><span class="token punctuation">,</span> touchMove<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">passive</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchend'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchcancel'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>gesture<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">endGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'touchmove'</span><span class="token punctuation">,</span> touchMove<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'touchend'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'touchcancel'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br />el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'touchstart'</span><span class="token punctuation">,</span> watchTouches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">touchMove</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> mp_init <span class="token operator">=</span> <span class="token function">midpoint</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> mp_curr <span class="token operator">=</span> <span class="token function">midpoint</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token function">distance</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token function">distance</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">rotation</span><span class="token operator">:</span> <span class="token function">angle</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">angle</span><span class="token punctuation">(</span>initial_touches<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">translation</span><span class="token operator">:</span> <span class="token punctuation">{</span> <br /> <span class="token literal-property property">x</span><span class="token operator">:</span> mp_curr<span class="token punctuation">.</span>x <span class="token operator">-</span> mp_init<span class="token punctuation">.</span>x<span class="token punctuation">,</span> <br /> <span class="token literal-property property">y</span><span class="token operator">:</span> mp_curr<span class="token punctuation">.</span>y <span class="token operator">-</span> mp_init<span class="token punctuation">.</span>y<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">origin</span><span class="token operator">:</span> mp_init<br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token function">doGesture</span><span class="token punctuation">(</span>gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
</details>
<h3>Safari mobile: touch or gesture events?</h3>
<p>Safari mobile (iOS and iPadOS) is the only browser that has support for both <code>GestureEvent</code> and <code>TouchEvent</code>, so which one should we choose for handling two-finger gestures?</p>
<p>On the one hand, enhancements Safari applies to <code>GestureEvent</code>s makes them feel smoother; on the other hand, <code>TouchEvent</code>s afford capturing the translation aspect of the gesture. We can't have both… can we?</p>
<p>Surprisingly, we can. Safari's <a href="https://developer.apple.com/documentation/webkitjs/touchevent">implementation of <code>TouchEvent</code></a> contains the same <code>scale</code> and <code>rotation</code> properties as the equivalent <code>GestureEvent</code>, if we ever want to use the browser's, rather than our own manually computed, values.</p>
<p>A check for the existence of the two interfaces lets us restrict gesture events to desktop Safari:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> Only attach GestureEvent listeners on macOS Safari.<br /> */</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>GestureEvent <span class="token operator">!==</span> <span class="token keyword">undefined</span> <span class="token operator">&&</span> window<span class="token punctuation">.</span>TouchEvent <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'gesturestart'</span><span class="token punctuation">,</span> …<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'gesturechange'</span><span class="token punctuation">,</span> …<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> container<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'gestureend'</span><span class="token punctuation">,</span> …<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2 id="apply-transform">
Applying the transformation
</h2>
<p>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.</p>
<h3>From gesture to DOMMatrix</h3>
<p>We can express the gesture as a transformation matrix, which uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix/">DOMMatrix</a> interface:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">gestureToMatrix</span><span class="token punctuation">(</span><span class="token parameter">gesture</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">DOMMatrix</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>x <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">,</span> gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>y <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">rotate</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>rotation <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">scale</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>scale <span class="token operator">||</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>To apply the DOM matrix to an HTML element, we simply add it to the element's style on the <code>transform</code> property. On SVG elements we can similarly set the element's <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform"><code>transform</code> attribute</a>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">applyMatrix</span><span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> matrix</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el <span class="token keyword">instanceof</span> <span class="token class-name">HTMLElement</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>transform <span class="token operator">=</span> matrix<span class="token punctuation">;</span><br /> <span class="token keyword">return</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el <span class="token keyword">instanceof</span> <span class="token class-name">SVGElement</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> el<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'transform'</span><span class="token punctuation">,</span> matrix<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Expected HTML or SVG element'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Applying the gesture origin</h3>
<p>The origin of the transformation for an element can be configured via the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin"><code>transform-origin</code> CSS property</a> or its <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform-origin">equivalent SVG attribute</a>. But we can actually factor it right into our transformation. The <a href="https://drafts.csswg.org/css-transforms/#transform-rendering">CSS Transforms Module Level 1</a> states:</p>
<blockquote>
<p>The transformation matrix is computed from the <code>transform</code> and <code>transform-origin</code> properties as follows:</p>
<ol>
<li>Start with the identity matrix.</li>
<li><strong>Translate by the computed X and Y of <code>transform-origin</code>.</strong></li>
<li>Multiply by each of the transform functions in <code>transform</code> property from left to right.</li>
<li><strong>Translate by the negated computed X and Y values of <code>transform-origin</code>.</strong></li>
</ol>
</blockquote>
<p>We can update the <code>gestureToMatrix()</code> function to reference an <code>origin</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">gestureToMatrix</span><span class="token punctuation">(</span><span class="token parameter">gesture<span class="token punctuation">,</span> origin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">DOMMatrix</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span>origin<span class="token punctuation">.</span>x<span class="token punctuation">,</span> origin<span class="token punctuation">.</span>y<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>x <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">,</span> gesture<span class="token punctuation">.</span>translation<span class="token punctuation">.</span>y <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">rotate</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>rotation <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">scale</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>scale <span class="token operator">||</span> <span class="token number">1</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span><span class="token operator">-</span>origin<span class="token punctuation">.</span>x<span class="token punctuation">,</span> <span class="token operator">-</span>origin<span class="token punctuation">.</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>You may be wondering why we are sending in <code>origin</code> as a second argument to the <code>gestureToMatrix()</code> function instead of just reading it off the <code>gesture</code> object. The reason for this is that the origin of the transformation is fixed at the midpoint of the <em>initial</em> touchpoints.</p>
<p>The origin doesn't change with each <code>doGesture()</code>. We compute it once, at the beginning of the gesture.</p>
<p>For HTML elements, we need to express the origin in relation to the element itself, so we read the element's <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">bounding client rectangle</a>. On the other hand, transformations to a SVG element relate to the element's nearest <code><svg></code> container, which establishes its coordinate system. The container's <code>getScreenCTM()</code> returns the matrix that maps from its coordinate system to screen coordinates, so we use its <code>inverse()</code> to express the transformation origin in terms of <code><svg></code> coordinates.</p>
<p>The unified <code>getOrigin()</code> function below works for both HTML and SVG elements:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">getOrigin</span><span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> gesture</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el <span class="token keyword">instanceof</span> <span class="token class-name">HTMLElement</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> rect <span class="token operator">=</span> el<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">x</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>origin<span class="token punctuation">.</span>x <span class="token operator">-</span> rect<span class="token punctuation">.</span>x<span class="token punctuation">,</span><br /> <span class="token literal-property property">y</span><span class="token operator">:</span> gesture<span class="token punctuation">.</span>origin<span class="token punctuation">.</span>y <span class="token operator">-</span> rect<span class="token punctuation">.</span>y<br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el <span class="token keyword">instanceof</span> <span class="token class-name">SVGElement</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> matrix <span class="token operator">=</span> el<span class="token punctuation">.</span>ownerSVGElement<span class="token punctuation">.</span><span class="token function">getScreenCTM</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">inverse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> pt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DOMPoint</span><span class="token punctuation">(</span>gesture<span class="token punctuation">.</span>origin<span class="token punctuation">.</span>x<span class="token punctuation">,</span> gesture<span class="token punctuation">.</span>origin<span class="token punctuation">.</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> pt<span class="token punctuation">.</span><span class="token function">matrixTransform</span><span class="token punctuation">(</span>matrix<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Expected HTML or SVG element'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>For transforms to work correctly on HTML elements, we need to jump through a couple of more hoops:</p>
<ul>
<li><strong>Set the element's origin to the top-left corner</strong> with <code>transform-origin: 0 0</code>. The default is the object's center at <code>50% 50%</code>.</li>
<li><strong>Clear the element's transform</strong> at the beginning of each gesture, before reading its <code>getBoundingClientRect()</code>, to obtain the origin in relation to the original, untransformed position of the element.</li>
</ul>
<p>These concerns are factored into the final solution below.</p>
<h3>Putting it all together</h3>
<p>Here's the code to apply the transformation matrix to the object (either HTML or SVG) on the <code>startGesture</code>, <code>doGesture</code> and <code>endGesture</code> callbacks.</p>
<p>Notice how we hold onto the object's initial matrix in <code>init_m</code> to multiply into the current matrix at every step of the gesture.</p>
<pre class="language-js"><code class="language-js"><br /><span class="token comment">/*<br /> Older versions of Safari expose transformation matrices <br /> on the `WebKitCSSMatrix` interface instead of `DOMMatrix`<br /> */</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>window<span class="token punctuation">.</span>DOMMatrix<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>WebKitCSSMatrix<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span>DOMMatrix <span class="token operator">=</span> window<span class="token punctuation">.</span>WebKitCSSMatrix<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Couldn't find a DOM Matrix implementation"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">let</span> origin<span class="token punctuation">;</span><br /><span class="token keyword">let</span> init_m <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DOMMatrix</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#target'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/*<br /> HTML elements have their `transform-origin` <br /> set to '50% 50%' by default, we need to <br /> reset it to the top-left corner.<br /> */</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>el <span class="token keyword">instanceof</span> <span class="token class-name">HTMLElement</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>transformOrigin <span class="token operator">=</span> <span class="token string">'0 0'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">startGesture</span><span class="token punctuation">(</span><span class="token parameter">gesture</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>el <span class="token keyword">instanceof</span> <span class="token class-name">HTMLElement</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> Clear the element's transform so we can <br /> measure its original position wrt. the screen.<br /><br /> (We don't need to restore it because it gets <br /> overwritten by `applyMatrix()` anyways.)<br /> */</span><br /> el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>transform <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> origin <span class="token operator">=</span> <span class="token function">getOrigin</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> gesture<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">applyMatrix</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> <span class="token function">gestureToMatrix</span><span class="token punctuation">(</span>gesture<span class="token punctuation">,</span> origin<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">multiply</span><span class="token punctuation">(</span>init_m<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">doGesture</span><span class="token punctuation">(</span><span class="token parameter">gesture</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">applyMatrix</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> <span class="token function">gestureToMatrix</span><span class="token punctuation">(</span>gesture<span class="token punctuation">,</span> origin<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">multiply</span><span class="token punctuation">(</span>init_m<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">endGesture</span><span class="token punctuation">(</span><span class="token parameter">gesture</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> init_m <span class="token operator">=</span> <span class="token function">gestureToMatrix</span><span class="token punctuation">(</span>gesture<span class="token punctuation">,</span> origin<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">multiply</span><span class="token punctuation">(</span>init_m<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">applyMatrix</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> init_m<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Test this code out in these demos:</p>
<ul>
<li><a href="https://danburzo.ro/demos/dom-gestures/demo-html.html">Demo: Gestures on an HTML element</a></li>
<li><a href="https://danburzo.ro/demos/dom-gestures/demo-svg.html">Demo: Gestures on a SVG element</a></li>
</ul>
<h2 id="further-refinements">Refining the implementation</h2>
<p>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?</p>
<p>Disabling some of these default behaviors disambiguates taps. If we don't need double-tap-to-zoom gesture, the browser can make taps work like clicks without introducing artificial delays.</p>
<p>This is the general idea behind <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action">the <code>touch-action</code> CSS property</a>. It instructs the browser to only apply <em>some</em> of its default behaviors when the user peforms one- or two-finger gestures: one-finger scrolling, pinch-zooming, or the <em>double-tap to zoom</em> behavior.</p>
<p>Depending on the exact needs of our implementation, we might apply <code>touch-action: pan-x pan-y</code> (which tells the browser to apply one-finger scrolling while ignoring all other gestures), or even <code>touch-action: none</code> to our container, in which case the responsibility is ours to implement all touch interactions. As a rule of thumb for <code>touch-action</code>, only disable the browser behaviors you plan to replace with custom gestures.</p>
<p>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.</p>
<p>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 <a href="https://danburzo.ro/dom-gestures/#questions-answers">Questions & Answers section</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this article we've looked at how we can treat DOM <code>GestureEvent</code>, <code>WheelEvent</code>, and <code>TouchEvent</code> uniformly to add support for two-finger gestures to web pages with some pretty good results across a variety of devices.</p>
<p>Some quick links:</p>
<ul>
<li><a href="https://danburzo.ro/demos/dom-gestures/logger.html">DOM gesture logger</a></li>
<li><a href="https://danburzo.ro/demos/dom-gestures/lib.js">Reference JavaScript implementation</a></li>
<li><a href="https://danburzo.ro/demos/dom-gestures/demo-html.html">Demo: Gestures on an HTML element</a></li>
<li><a href="https://danburzo.ro/demos/dom-gestures/demo-svg.html">Demo: Gestures on a SVG element</a></li>
<li><a href="https://danburzo.ro/demos/dom-gestures/demo-buttons.html">Demo: Combined gestures and zoom buttons on an HTML element</a></li>
</ul>
<p>This supplemental material is also published on GitHub in the <a href="https://github.com/danburzo/ok-zoomer"><code>danburzo/ok-zoomer</code></a> repository.</p>
<hr />
<h2 id="questions-answers">Questions & Answers</h2>
<h3 id="current-scale">How do I find the element's current scale?</h3>
<p>To know the element's scale at any given moment, we need to either:</p>
<ul>
<li>keep track of all the gestures we've applied and accumulate the scales in a variable, or</li>
<li>somehow extract the scale from the element's current transformation matrix.</li>
</ul>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix">DOMMatrix</a> interface doesn't have a method to retrieve individual components such as scale, translation, or rotation from a matrix, so we have to implement it ourselves.</p>
<p>To that end we can use the algorithm described in Spencer W. Thomas's article <a href="https://doi.org/10.1016/B978-0-08-050754-5.50069-4">Decomposing a matrix into simple transformations</a>, published in <em>Graphics Gems II</em> (1991). A pseudo-code version is published in the <a href="https://www.w3.org/TR/css-transforms-1/#decomposing-a-2d-matrix">CSS Transforms specification</a>, and a C implementation is <a href="https://github.com/erich666/GraphicsGems/blob/master/gemsii/unmatrix.c">available on GitHub</a>.</p>
<p>The algorithm handles cases that don't apply to our situation — non-uniform scaling, skewing, etc. — so if we're only interested in extracting the uniform scale, we can get away with a one-liner:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">getScaleFromMatrix</span> <span class="token operator">=</span> <span class="token parameter">matrix</span> <span class="token operator">=></span> Math<span class="token punctuation">.</span><span class="token function">hypot</span><span class="token punctuation">(</span>matrix<span class="token punctuation">.</span>a<span class="token punctuation">,</span> matrix<span class="token punctuation">.</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And if you <em>are</em> interested in the general method, you can find some <a href="https://github.com/search?q=unmatrix">JavaScript implementations on GitHub</a>.</p>
<p>Knowing the current scale is useful in a variety of cases:</p>
<p><strong>Zooming to a specific scale.</strong> To produce an absolute scale, such as <code>50%</code> or <code>400%</code>, we can express it as relative to the current scale and create a gesture for it:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> current_scale <span class="token operator">=</span> <span class="token function">getScaleFromMatrix</span><span class="token punctuation">(</span>current_transform_matrix<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> gesture <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> <span class="token number">0.5</span> <span class="token operator">/</span> current_scale<span class="token punctuation">,</span> <span class="token comment">// 50% scale</span><br /> <span class="token comment">// …</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token comment">// apply gesture</span></code></pre>
<p><strong>Limiting the zoom level to a minimum/maximum.</strong> 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:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">clampScale</span><span class="token punctuation">(</span><span class="token parameter">gesture</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> s <span class="token operator">=</span> <span class="token function">getScaleFromMatrix</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> proposed_scale <span class="token operator">=</span> gesture<span class="token punctuation">.</span>scale <span class="token operator">*</span> s<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>proposed_scale <span class="token operator">></span> <span class="token number">4</span> <span class="token operator">||</span> proposed_scale <span class="token operator"><</span> <span class="token number">0.25</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>gesture<span class="token punctuation">,</span><br /> <span class="token literal-property property">scale</span><span class="token operator">:</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token number">0.25</span><span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">,</span> proposed_scale<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> s<br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> gesture<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>You can see both these examples in action in <a href="https://danburzo.ro/demos/dom-gestures/demo-buttons.html">Demo: Combined gestures and zoom buttons on an HTML element</a>.</p>
<hr />
<h2 id="revisions">Revisions</h2>
<p><strong>Nov 22, 2020:</strong> Published a first draft of this article <a href="https://dev.to/danburzo/pinch-me-i-m-zooming-gestures-in-the-dom-a0e">on dev.to</a>.</p>
<p><strong>Nov 15, 2021:</strong> Updated the <a href="https://danburzo.ro/dom-gestures/#handle-gesture-events">Safari gesture events section</a> to note that Safari 15 now also produces <code>wheel</code> events for pinch-zoom gestures, in line with the other major browsers, as well as to point out the double-<code>gestureend</code> bug.</p>
<p><strong>Jan 10, 2022:</strong> Fixed some typos, improved wheel-to-gesture code, and added a <em>Questions & Answers</em> section.</p>
<p><strong>May 19, 2022:</strong> Fixed the SVG case in <code>applyMatrix()</code> for Chrome, which doesn't currently support a <code>DOMMatrix</code> argument to <code>SVGTransformList.createSVGTransformFromMatrix()</code>. (<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=835431">Chromium#835431</a>)</p>
<hr />
<p><em>Thanks to Arne, Stephane and Sam for helping improve this article.</em></p>
On some features in Safari 152021-09-29T00:00:00Zhttps://danburzo.ro/on-safari-15/<p>A new major version of Safari was pitched back in June at the Apple WWDC 2021 event. In the <a href="https://developer.apple.com/videos/play/wwdc2021/10029/">Design for Safari 15</a> (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.</p>
<p>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 <a href="https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes">release notes</a> hint that it was initially planned for macOS Monterey.</p>
<p>Now that I've had the chance to play around with the new version, here are some personal highlights from the release notes.</p>
<h2>More ways to specify colors</h2>
<p>Safari 15 is the first browser to support the new color syntaxes defined in <a href="https://drafts.csswg.org/css-color/">the CSS Color Level 4 spec</a>.</p>
<p>HSL, and its close relative HSV, rework the RGB color model (basically <a href="https://commons.wikimedia.org/wiki/File:Hsl-and-hsv.svg">squishing the RGB cube into a cylinder</a>) to separate colors into more intuitive dimensions. For a long time, <code>hsl()</code> has been the only alternative to <code>rgb()</code> for specifying colors, and is unquestionably an improvement: it's <em>very</em> hard to imagine a RGB color by looking at the R/G/B components, and it'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's easier to match a target color by adjusting Hue, Saturation, and Lightness sliders. (You can try this <a href="https://michaelbach.de/ot/col-match/index.html">old-school color matching game</a> in RGB vs. HSL mode to see the difference.)</p>
<p>The <a href="http://alvyray.com/Papers/CG/HWB_JGTv208.pdf">HWB color space</a> 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's available under the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb()"><code>hwb()</code></a> syntax.</p>
<p>HSL, HSV, and HWB all use attributes that are easier to reason about, but they're not really aligned to <em>how</em> we perceive colors. <a href="https://wildbit.com/blog/2021/09/16/accessible-palette-stop-using-hsl-for-color-systems">HSL has its drawbacks</a> and <a href="https://www.vis4.net/blog/2011/12/avoid-equidistant-hsv-colors/">so does HSV</a> and, by extension, so does HWB. The <a href="https://en.wikipedia.org/wiki/CIELAB_color_space">CIELAB color space</a> is much more perceptually uniform and now we can use it in CSS, with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab()"><code>lab()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch()"><code>lch()</code></a> forms.</p>
<p>Finally, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color()">the <code>color()</code> function notation</a>, which debuted in Safari 10.1 with support for the <code>srgb</code> and <code>display-p3</code> predefined color spaces, is supplemented in Safari 15 with a few more:</p>
<ul>
<li><code>a98-rgb</code>, the compatible-with but not, <a href="https://www.adobe.com/digitalimag/adobergb.html">legally speaking</a>, the actual <a href="https://en.wikipedia.org/wiki/Adobe_RGB_color_space">Adobe RGB (1998)</a> color space;</li>
<li><code>prophoto-rgb</code>, the <a href="https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space">ProPhoto RGB</a> color space;</li>
<li><code>rec2020</code>, the <a href="https://en.wikipedia.org/wiki/Rec._2020">Rec. 2020</a> color space;</li>
<li><code>xyz</code>, the <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space">CIE XYZ color space</a> with a <a href="https://en.wikipedia.org/wiki/Standard_illuminant">D50 white point</a>.</li>
</ul>
<h2>A redesigned color picker</h2>
<p>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:</p>
<ul>
<li>the swatches panel from the previous version;</li>
<li>a hue/lightness spectrum for picking fully-saturated colors;</li>
<li>red/green/blue sliders and a hex input.</li>
</ul>
<figure>
<a href="https://danburzo.ro/img/safari-15/safari-15-ios-color-picker.png"><img class="cover" src="https://danburzo.ro/img/safari-15/safari-15-ios-color-picker.png" width="3457" height="1557" /></a>
<figcaption>The three panels in the mobile Safari 15 color picker.</figcaption>
</figure>
<p>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 <code>display-p3</code>, which sort of makes sense (after all, the screen is able to render <code>display-p3</code> colors) but on the other hand there's no concept of <strong><code>display-p3</code> hex code</strong> in CSS, which may confuse folks.</p>
<p>Since <code><input type='color'></code> only supports six-digit hex values, an opacity slider is not included in the picker <a href="https://developer.apple.com/design/human-interface-guidelines/ios/controls/color-wells/">like in native apps</a>. On the other hand, one cool feature <a href="https://developer.apple.com/design/human-interface-guidelines/macos/selectors/color-wells/">adopted from macOS</a> is the ability to drag & drop one color well atop another to apply the value:</p>
<video loop="" controls="" width="246">
<source src="https://danburzo.ro/img/safari-15/safari-15-ios-color-wells.mp4" />
</video>
<h2 id="pull-to-refresh">A built-in pull-to-refresh gesture</h2>
<p>Safari 15 introduces a built-in pull-to-refresh gesture on mobile that's pretty handy.</p>
<p>I see it as especially beneficial to progressive web apps, where you don'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.</p>
<p>It also seems that you can't disable the gesture using the <a href="https://danburzo.ro/css-overscroll-behavior/"><code>overscroll-behavior</code></a> CSS property, as it's not currently implemented (<a href="https://bugs.webkit.org/show_bug.cgi?id=176454">WebKit#176454</a>). Websites that implement their own similar gesture may suffer from a jarring double-refresh effect. If that's the case, the only course of action is to disable the custom behavior. Since <code>overscroll-behavior-y</code> is <a href="https://developers.google.com/web/updates/2017/11/overscroll-behavior">the most likely vehicle</a> for preventing the built-in gesture when it becomes possible, you can use this JavaScript snippet to target problematic browser versions:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> Feature-detect Safari versions <br /> with an unpreventable pull-to-refresh gesture.<br /><br /> How it works: <br /><br /> Safari 15 introduced support for the `xyz` color space,<br /> while simultaneously being the last major browser engine <br /> lacking support for `overscroll-behavior`.<br /> */</span><br /><span class="token keyword">const</span> isSafariWithUnstoppablePullRefresh <span class="token operator">=</span> <br /> window<span class="token punctuation">.</span><span class="token constant">CSS</span> <span class="token operator">&&</span> <br /> <span class="token constant">CSS</span><span class="token punctuation">.</span>supports <span class="token operator">&&</span><br /> <span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">'(color: color(xyz))'</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <br /> <span class="token operator">!</span><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">'(overscroll-behavior-y: contain)'</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <br /> window<span class="token punctuation">.</span>TouchEvent <span class="token operator">&&</span> <br /> <span class="token operator">!</span>navigator<span class="token punctuation">.</span>standalone<span class="token punctuation">;</span><br /><br /><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isSafariWithUnstoppablePullRefresh<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// initialize custom pull-to-refresh gesture</span><br /><span class="token punctuation">}</span></code></pre>
<h2>The <code>theme-color</code> HTML meta tag</h2>
<p>Across platforms, Safari 15 is using <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color">the <code>theme-color</code> meta tag</a> to tint the browser UI.</p>
<p>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 <a href="https://htmlhead.dev/">other things that go in <code><head></code></a>, to avoid weird color combos.</p>
<p>Manuel Matuzović explores what you can and can't do with <code>theme-color</code> in <a href="https://css-tricks.com/meta-theme-color-and-trickery/">this CSS-Tricks article</a>.</p>
<h2 id="new-tab-bar">A new tab bar</h2>
<p>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.</p>
<p>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 <q>adjusted environment variable calculations where appropriate to adjust for the safe area of the new iOS design</q>. As <a href="https://lukechannings.com/blog/2021-06-09-does-safari-15-fix-the-vh-bug/">documented by Luke Channings</a>, Safari 15 Beta did afford a combination of <code>height: 100vh</code> and <code>env(safe-area-inset-bottom)</code> to place a toolbar at the bottom of the screen without risking it being hidden behind the URL bar.</p>
<p>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 <span class="ui">Aa</span> menu is becoming a bit of a kitchen sink cabinet, all in all I feel like this is a step in the right direction.</p>
<p>What about the changes to <code>env()</code>? As seen <a href="https://danburzo.ro/demos/100vh-and-env.html">in this demo</a>, they seem to have been reverted for the case we were interested in: <code>env(safe-area-inset-bottom)</code> is <code>0</code> when floating toolbar is shown, and it still obstructs the bottom part of the page.</p>
<blockquote>
<p><strong>For reference:</strong> Here's <a href="https://danburzo.ro/reference/browser-tests/env/">a browser test</a> that shows the computed value of available <code>env()</code> properties, as well as <code>100vh</code> and the <code>-webkit-fill-available</code> height.</p>
</blockquote>
<p>The way out seems to be the <a href="https://drafts.csswg.org/css-values-4/#viewport-relative-lengths">new viewport units</a>, in particular <code>dvh</code> (dynamic viewport height). Bramus has <a href="https://www.bram.us/2021/07/08/the-large-small-and-dynamic-viewports/">an explainer</a> on these units. (<strong>Nov 24, 2021 update:</strong> Safari TP 135 has just <a href="https://webkit.org/blog/12040/release-notes-for-safari-technology-preview-135/">added support for them</a>).</p>
How it's made: Watch/Star/Fork2021-09-12T09:01:00Zhttps://danburzo.ro/wsf-making-of/<p>I thought I'd write a bit about my awkward but workable flow for adding several dozens of links to each <a href="https://danburzo.ro/watchstarfork/">Watch/Star/Fork</a> edition, and my usage of bookmarks in general.</p>
<h2>From many inboxes to one</h2>
<p>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.</p>
<p>Firefox has the best ergonomics for bookmark people, so that's where I hang out most. Whenever I see something interesting and/or potentially useful, I drag & drop the tab onto my <a href="https://support.mozilla.org/en-US/kb/bookmarks-toolbar-display-favorite-websites">bookmarks toolbar</a>, which has little bins for sorting everything neatly. If it's something I feel belongs here, I drop it in the <code>wsf</code> folder.</p>
<p>Before, if an online service had some sort of star/like/favorite/upvote function that gets saved and attached to my profile, I'd use it to bookmark things I wanted to get back to later. But with so many inboxes, I'd rarely actually revisit them, so I've started to direct everything towards actual bookmarks.</p>
<p>To get the data out of the various places in the <a href="https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753582(v=vs.85)">weird de facto standard bookmark format</a>, I employed a variety of tools, some of which — <a href="https://github.com/danburzo/hred">hred</a> and <a href="https://github.com/danburzo/nbf">nbf</a> — I wrote for the purpose. The <a href="https://github.com/danburzo/nbf">nbf repository page</a> collects a few recipes for things like GitHub stars or NetNewsWire favorites.</p>
<p>As a grand final gesture I tried removing my historical "bookmarks" 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 <a href="https://twitter.com/danburzo/status/1421076805180657664">unreachable Twitter likes</a>. Oops!</p>
<p>I now use temporary Twitter likes to show my interest & appreciation, but once a week I'll go through them, bookmark any useful links, and then reset everything. (I know there's the separate <em>Bookmarks</em> feature in Twitter, but I prefer to send a little signal of gratitude.) And with the new system in place, my <a href="https://github.com/danburzo?tab=stars">GitHub stars page</a> is always empty.</p>
<h2>Safari's Reading List as syncing tool</h2>
<p>To sync bookmarks between my phone and my laptop I use, out of all the possible technology available in the year 2021, Safari's Reading List. A bit like in this funny, insightful tweet I saw on my timeline today:</p>
<blockquote>
<p>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</p>
<p>— <a href="https://twitter.com/sievish/status/1436351770037559300">@sievish</a> on Sep 10, 2021</p>
</blockquote>
<p>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 <samp>Add to reading list</samp> action from the iOS sharing panel, so it'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.</p>
<p>One snag I have with this setup is that Safari sometimes decides, out of the blue and without indication, to <a href="https://twitter.com/danburzo/status/1118980089696391184">stop saving web pages</a> to the Reading List. I haven't traced the reason behind it, and it'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'm not too precious about the odd bookmark going missing here and there. But just so you know.</p>
<p>From macOS Safari to Firefox is a short trip: nowadays Safari provides an <samp>Export bookmarks</samp> function that includes the Reading List, and before that I used to maintain <a href="https://github.com/danburzo/export-safari-reading-list">a little tool</a> to extract the bookmarks from Safari's <code>Bookmarks.plist</code> file.</p>
<h2>From bookmark to Markdown</h2>
<p>The last leg of the journey from browser tabs to blog post is turning the <code>wsf</code> bookmarks folder into the Markdown that powers this site.</p>
<p>As I mentioned earlier, Firefox has great affordances around bookmarks. One of them is being able to <samp>Right-click → Copy</samp> or drag & drop entire folders. Pasting a folder into <a href="https://evercoder.github.io/clipboard-inspector/">Clipboard inspector</a>, it transpires that Firefox puts into the clipboard, as the <code>text/html</code> entry, actual, honest-to-goodness HTML (in the Netscape Bookmark Format), similar to what you'd get if you used the <em>Export bookmarks to HTML</em> function.</p>
<p>On the other hand, there several online one-page tools to convert HTML to Markdown; I've settled on <a href="https://mixmark-io.github.io/turndown/">Turndown</a>, which works nicely with the default settings. There's a small hoop to jump through: when you paste a Firefox folder into Turndown's <code><textarea></code> element, it gets the <code>text/plain</code> clipboard entry. I've submitted <a href="https://github.com/mixmark-io/turndown/pull/399">a pull request</a> to grab the <code>text/html</code> data when it exists, but in the meantime I use Clipboard Inspector's <em>Copy as plain text</em> function.</p>
<p>One thing I still do by hand is add the authors' names next to each link, but I'm glad I get at least a head start on all of the title/link pairs.</p>
Use code to explore and change JavaScript files2021-09-08T00:00:00Zhttps://danburzo.ro/javascript-codemods/<p>A while back I wrote, as an exercise in minimalism, the <a href="https://github.com/danburzo/nano-i18n"><code>nano-i18n</code></a> library for localizing strings. You write internationalized strings with a template tag:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> t <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'nano-i18n'</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token string">'Dan'</span><span class="token punctuation">;</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>t<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>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're easier to find when you want to translate them. Pretty straightforward stuff.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> t<span class="token punctuation">,</span> config <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'nano-i18n'</span><span class="token punctuation">;</span><br /><span class="token function">config</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token function-variable function">log</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">msg<span class="token punctuation">,</span> key</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Missing: "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token string">'Dan'</span><span class="token punctuation">;</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>t<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// => Missing: "Hello, {}!"</span></code></pre>
<p>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.</p>
<p>Instead, let's see how we can automate our way out of this predicament, and extract from a JavaScript codebase all string literals tagged with the <code>t</code> template with a script.</p>
<hr />
<p>For answering basic questions about a codebase, or for simple code alterations, I would normally first attempt a well-crafted regular expression. I'll paste it into Sublime Text's <em>Search & Replace</em> interface, or <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> (look at the <code>--only-matching</code> and <code>--replace</code> options), or some combination standard command-line tools I'll never remember unless <a href="https://danburzo.ro/toolbox/unix-cli">I have it written down somewhere</a>.</p>
<p>This is not one of those times. The <code>t</code> tag can interpolate basically any JavaScript expression within the translation, so it's impossible to deploy a regex to match all the patterns that might occur in the typical codebase. We need a tool that <em>understands</em> the syntax we're working with.</p>
<h2>A short survey of the tools available</h2>
<p>There are quite a few options for parsing JavaScript: there's <a href="https://github.com/jquery/esprima"><code>esprima</code></a>, <a href="https://github.com/acornjs/acorn"><code>acorn</code></a>, and <a href="https://babeljs.io/docs/en/babel-parser.html"><code>@babel/parser</code></a>. These parsers produce an Abstract Syntax Tree (AST) from a JavaScript string, either adhering the <a href="https://github.com/estree/estree"><code>ESTree</code></a> format, or to something fairly similar.</p>
<p>Further along, <a href="https://github.com/benjamn/recast"><code>recast</code></a> helps you alter the AST, and then remake it into a string. A combination of <a href="https://github.com/davidbonnet/astring"><code>astring</code></a> and <a href="https://github.com/davidbonnet/astravel"><code>astravel</code></a> serve a similar purpose. But how do you know <em>how</em> to alter the AST? <a href="https://astexplorer.net/">AST Explorer</a> lets you type in sample JavaScript code and shows you the resulting tree as an interactive, navigable JSON.</p>
<p>Finally, <a href="https://github.com/facebook/jscodeshift"><code>jscodeshift</code></a> is a command-line tool written in JavaScript that uses <code>recast</code> and helps with bits of admin, such as:</p>
<ul>
<li>picking up on your Babel configuration if you have one; this is handy if you're using plugins for more exotic syntax.</li>
<li>running things in parallel to speed up processing a large codebase.</li>
</ul>
<p>It's used for a variety of purposes, such as <a href="https://github.com/reactjs/react-codemod">keeping your React components up to date</a> with the occasional changes in React's API.</p>
<p>You could start with the tools from any level on the abstraction ladder, and build up from there. In this particular case, I'd rather not get bogged down in unrelated complexities & gotchas and focus on the unique aspects of the task at hand. With a few tweaks here and there, we can adapt <code>jscodeshift</code> to not modify JavaScript files, but instead gather statistics on them, so that's going to be the tool of choice today.</p>
<h2>Analyzing code with <code>jscodeshift</code></h2>
<p>Install <code>jscodeshift</code> from npm and add a script to your <code>package.json</code>:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"gather"</span><span class="token operator">:</span> <span class="token string">"node node_modules/.bin/jscodeshift --help"</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Running the script with <code>npm run gather</code> 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't usually need to run Node.js CLI tools with <code>node</code>, but this form is necessary at the time of writing to work around <a href="https://github.com/facebook/jscodeshift/issues/424">a quirk in <code>jscodeshift</code></a>.</p>
<p>To actually do something, the tool takes a JavaScript file containing the transform we want to perform on our JavaScript files, under the <code>--transform</code> command-line argument. It also needs a folder in which to look for files, so a proper invocation looks like this (with the <code>node</code> part omitted):</p>
<pre class="language-bash"><code class="language-bash">jscodeshift <span class="token parameter variable">--transform</span><span class="token operator">=</span>gather-translatables.js js/</code></pre>
<blockquote>
<p><strong>A word of caution:</strong> <code>jscodeshift</code> has the ability to overwrite your JavaScript files. Make sure you use a version control system such as Git on the codebase you're working with, and that you don't have any pending changes to commit, before messing around with transforms.</p>
</blockquote>
<p>The transform is listed below. The API looks a bit like jQuery, with chained methods on a collection of AST nodes.</p>
<p>Technically this is the point where you'd modify the AST to write back to the file, the task at which <code>jscodeshift</code> excels. We only want to gather some statistics, so we <strong>don't return anything</strong> from the transform function — this instructs <code>jscodeshift</code> to keep the source files intact.</p>
<p>Instead we use the <code>report</code> function that writes its argument to the console output.</p>
<p><strong>gather-translatables.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> The jscodeshift transform<br /> -------------------------<br /> */</span><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">fileInfo<span class="token punctuation">,</span> api</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> jscodeshift<span class="token punctuation">,</span> report <span class="token punctuation">}</span> <span class="token operator">=</span> api<span class="token punctuation">;</span><br /> <span class="token function">jscodeshift</span><span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>source<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>jscodeshift<span class="token punctuation">.</span>TaggedTemplateExpression<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>isTranslationTag<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">path</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> key <span class="token operator">=</span> <span class="token function">toTranslationKey</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">report</span><span class="token punctuation">(</span><span class="token string">'\n'</span> <span class="token operator">+</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/*<br /> Matches template literals tagged with "t":<br /><br /> t`Hello, world!`<br /> */</span><br /><span class="token keyword">function</span> <span class="token function">isTranslationTag</span><span class="token punctuation">(</span><span class="token parameter">path</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> name <span class="token punctuation">}</span> <span class="token operator">=</span> path<span class="token punctuation">.</span>node<span class="token punctuation">.</span>tag<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> type <span class="token operator">===</span> <span class="token string">'Identifier'</span> <span class="token operator">&&</span> name <span class="token operator">===</span> <span class="token string">'t'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/*<br /> Converts a tagged template literal <br /> to a `nano-i18n` translation key:<br /><br /> t`Hello, ${stranger}!`<br /><br /> Becomes:<br /><br /> 'Hello, ${0}!'<br /> */</span><br /><span class="token keyword">function</span> <span class="token function">toTranslationKey</span><span class="token punctuation">(</span><span class="token parameter">path</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> quasis <span class="token punctuation">}</span> <span class="token operator">=</span> path<span class="token punctuation">.</span>node<span class="token punctuation">.</span>quasi<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> quasis<br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">q<span class="token punctuation">,</span> idx</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> q<span class="token punctuation">.</span>value<span class="token punctuation">.</span>raw <span class="token operator">+</span> <span class="token punctuation">(</span>q<span class="token punctuation">.</span>tail <span class="token operator">?</span> <span class="token string">''</span> <span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">{</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>idx<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>To see what I need to match and how to process the AST in the transform function, I've looked at the structure of a typical tagged template literal in <a href="https://astexplorer.net/">AST Explorer</a>, with this sample JavaScript code:</p>
<pre class="language-js"><code class="language-js">t<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>stranger<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<details>
<summary>AST Explorer output</summary>
<p>This is a simplified JSON output with just the essential properties for each node.</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"TaggedTemplateExpression"</span><span class="token punctuation">,</span><br /> <span class="token property">"tag"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"Identifier"</span><span class="token punctuation">,</span><br /> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"t"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"quasi"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"TemplateLiteral"</span><span class="token punctuation">,</span><br /> <span class="token property">"expressions"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"Identifier"</span><span class="token punctuation">,</span><br /> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"stranger"</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token property">"quasis"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"TemplateElement"</span><span class="token punctuation">,</span><br /> <span class="token property">"value"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"raw"</span><span class="token operator">:</span> <span class="token string">"Hello, "</span><span class="token punctuation">,</span><br /> <span class="token property">"cooked"</span><span class="token operator">:</span> <span class="token string">"Hello, "</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"tail"</span><span class="token operator">:</span> <span class="token boolean">false</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"TemplateElement"</span><span class="token punctuation">,</span><br /> <span class="token property">"value"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"raw"</span><span class="token operator">:</span> <span class="token string">"!"</span><span class="token punctuation">,</span><br /> <span class="token property">"cooked"</span><span class="token operator">:</span> <span class="token string">"!"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"tail"</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
</details>
<p>The transform does what we want it to do — it extracts all the translation keys used in any JavaScript file from the the <code>js/</code> folder. The AST processing part is done.</p>
<p>Let's turn our attention to how to shape this raw data we've just produced into something usable. Invoking the <code>gather</code> script, we can see that the output of <code>report()</code> is peppered with miscellaneous output from the tool, some of it colored. To clean up the output, we can use <code>jscodeshift</code>'s <code>--no-color</code> and <code>--silent</code> flags, which suppress the color and the informational output, respectively.</p>
<details>
<summary>Updated <code>gather</code> script in <code>package.json</code></summary>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"gather"</span><span class="token operator">:</span> <span class="token string">"node node_modules/.bin/jscodeshift --no-color --silent --transform=gather-translatables.js js/"</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
</details>
<p>Running the script again produces a cleaner output as a series of items with the following structure:</p>
<pre><code> REP js/path/to/hello.js
Hello, ${0}!
</code></pre>
<p>This form is not ideal, but the data is good enough to pipe into a separate script to achieve its final form. Here's a small Node.js script that does two things:</p>
<ul>
<li>the first part 'slurps' all the content from <code>stdin</code> (Sindre Sorhus has it packaged as <a href="https://github.com/sindresorhus/get-stdin">get-stdin</a>);</li>
<li>that content is then split into a flat list of unique, sorted translation keys.</li>
</ul>
<p><strong>sort-gathered.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> stdin <span class="token punctuation">}</span> <span class="token operator">=</span> process<span class="token punctuation">;</span><br />stdin<span class="token punctuation">.</span><span class="token function">setEncoding</span><span class="token punctuation">(</span><span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/*<br /> Accumulate the input from `stdin`<br /> into the `content` string.<br /> */</span><br /><span class="token keyword">let</span> content <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br />stdin<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'readable'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> chunk<span class="token punctuation">;</span><br /> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>chunk <span class="token operator">=</span> stdin<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> content <span class="token operator">+=</span> chunk<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/*<br /> When finished, process `content`.<br /> */</span><br />stdin<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'end'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token function">analyze</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> items <span class="token operator">=</span> content<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\n? REP .*\n</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">/*<br /> Unique keys, sorted alphabetically<br /> */</span><br /> <span class="token keyword">const</span> unique <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span>items<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>unique<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And here's the final <code>gather</code> script, piped into <code>sort-gathered.js</code>:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"gather"</span><span class="token operator">:</span> <span class="token string">"node node_modules/.bin/jscodeshift --no-color --silent --transform=gather-translatables.js js/ | node sort-gathered.js"</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><strong>P.S.</strong> <a href="https://twitter.com/Cykelero/status/1436031156860952580">Nathan points out</a> on Twitter that instead of piping into the second <code>sort-gathered.js</code> script, we could use <code>jscodeshift</code>'s <a href="https://github.com/facebook/jscodeshift/issues/398">undocumented JavaScript API</a>.</p>
<h2 id="changing-js">
<a href="https://danburzo.ro/javascript-codemods/#changing-js">#</a> Changing JavaScript files
</h2>
<p>Finally, let's use <code>jscodeshift</code> for the purpose for which it has been designed: to actually change JavaScript files.</p>
<p>I've recently switched <a href="https://culorijs.org/"><code>culori</code></a> to use native ES modules in Node.js for its inaugural <code>1.x</code> release and realized — by carefully reading <a href="https://nodejs.org/api/packages.html">the documentation</a>, haha just kidding, I promptly got a couple of hundred error messages — that all the imports <strong>need to use the full path</strong>, including the <code>.js</code> extension.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// From this:</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> interpolate <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../interpolate'</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// …to this:</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> interpolate <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../interpolate.js'</span><span class="token punctuation">;</span></code></pre>
<p>Normally at this point I would despair at the prospect of changing hundreds of declarations by hand, but it was effortless with a transform:</p>
<p><strong>imports-add-ext.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token comment">/*<br /> jscodeshift transform: adds the '.js' extension<br /> to all import declarations with relative specifiers:<br /><br /> From './file' to './file.js', and<br /> from '../file' to '../file.js'.<br /> */</span><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">fileInfo<span class="token punctuation">,</span> api</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> j <span class="token operator">=</span> api<span class="token punctuation">.</span>jscodeshift<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token function">j</span><span class="token punctuation">(</span>fileInfo<span class="token punctuation">.</span>source<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>j<span class="token punctuation">.</span>ImportDeclaration<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">path</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> <span class="token punctuation">{</span> source <span class="token punctuation">}</span> <span class="token operator">=</span> path<span class="token punctuation">.</span>node<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>source<span class="token punctuation">.</span>value<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^\.{1,2}\/</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> source<span class="token punctuation">.</span>value <span class="token operator">+=</span> <span class="token string">'.js'</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">toSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-bash"><code class="language-bash"><span class="token function">node</span> node_modules/.bin/jscodeshift <span class="token parameter variable">--transform</span><span class="token operator">=</span>imports-add-ext.js src/ test/</code></pre>
<p>The beauty of writing your own transforms is they only have to be as comprehensive as the task requires. In fact, after <a href="https://github.com/Evercoder/culori/commit/151281cc9e3ac37c1d261139e701e62f673432b6">changing the <code>import</code> declarations</a> I realized I needed to <a href="https://github.com/Evercoder/culori/commit/10327ea0e7de028a19f5f7ba3ce29f725070822c">address the <code>export</code> declarations</a> as well. Eh.</p>
<h2>Further reading</h2>
<p>A few links from the bookmark archive, filtered for "codemods", that you may find useful:</p>
<ul>
<li><a href="https://egghead.io/blog/codemods-with-babel-plugins">Codemods with Babel Plugins</a> (2021) by Laurie Barth;</li>
<li><a href="https://skovy.dev/codemod-workflow/">My Workflow for Codemods</a>(2021) by Spencer Miskoviak;</li>
<li><a href="https://www.reaktor.com/blog/an-introduction-to-codemods/">An Introduction to Codemods: Refactoring JavaScript with JavaScript</a>(2019) by Tijn Kersjes;</li>
<li><a href="https://engineering.squarespace.com/blog/2018/building-a-system-for-front-end-translations">Building a System for Front-End Translations</a>(2018) by Dan Na;</li>
<li><a href="https://padraig.io/automate-refactoring-jscodeshift/">Automate refactoring with <code>jscodeshift</code></a>(2018) by Patrick Owens;</li>
<li><a href="https://github.com/cpojer/js-codemod">cpojer/js-codemod</a>, a collection of <code>jscodeshift</code> scripts to transform code to next generation JS.</li>
</ul>
How I digitize books2021-07-09T00:00:00Zhttps://danburzo.ro/digitizing-books/<p>One of my current passions is to publish interesting public domain Romanian books on <a href="https://llll.ro/">llll.ro</a>. The website doesn'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.</p>
<p>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've settled (for now) to bring books online.</p>
<p>In short, the process entails:</p>
<ol>
<li>photographing the pages;</li>
<li>performing Optical Character Recognition (<abbr>OCR</abbr>) on them;</li>
<li>cleaning up and formatting the text.</li>
</ol>
<h2>Image acquisition</h2>
<p>The first step is to capture clear images of each page. While <abbr title="Optical Character Recognition">OCR</abbr> 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.</p>
<p>I use the (free) <a href="https://evernote.com/products/scannable/">Scannable app</a> on iOS to capture, crop, and perspective-correct the pages all in one go. When I'm happy with the crops, I use <span class="ui">Send → Share → AirDrop</span> to transfer the set of images to my computer — just make sure the selected format is PNG, not PDF.</p>
<figure>
<img src="https://danburzo.ro/img/scannable-blecher.jpg" alt="A page from a Max Blecher book processed with Scannable" width="300" />
<figcaption>A sample image produced by Scannable (<a href="https://danburzo.ro/img/scannable-blecher.jpg">see larger size</a>).</figcaption>
</figure>
<h2>Optical character recognition</h2>
<p>Next, we put the images to good use. For OCR to be truly useful, it has to be <em>very</em> accurate; otherwise, it's quicker and less frustrating to transcribe everything by hand than to fix an Unicode character soup.</p>
<p>For the kinds of texts I'm digitizing — Romanian, possibly using obsolete othography and diacritical marks — the offline OCR tools I've tested do a lukewarm job. Online services, for the most part, aren't much better, with one exception: <a href="https://cloud.google.com/vision/">Google's Vision API</a> provides near-flawless results. It'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.</p>
<p>I wrote a little web tool called <a href="https://llll.ro/tools/vizor">Vizor</a> 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.</p>
<p>The API's JSON response the text is segmented in a hierarchy of <code>Pages</code>, <code>Blocks</code>, <code>Paragraphs</code>, <code>Words</code> and <code>Symbols</code>. Vizor stitches them together according to your needs: you can either preserve the line breaks (<em>Verse</em>), or obtain nice, flowing paragraphs (<em>Prose</em>).</p>
<h2>Cleaning up the text</h2>
<p>I bring the plain text into Sublime Text and run it through <a href="https://llll.ro/meta/ocr-checklist">a series of checks I've written down</a>. These are a series of regular expressions (<em>regexes</em>) that I plop into Sublime Text'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.</p>
<p>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 <a href="https://github.com/danburzo/percollate">percollate</a>:</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># Produce a PDF file:</span><br />percollate pdf /path/to/doc.html<br /><br /><span class="token comment"># Produce an EPUB file:</span><br />percollate epub /path/to/doc.html</code></pre>
<p>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're easy to review at the end.</p>
<p>This process allows me to produce high-quality hypertext out of a moderately-sized book in a few evenings' work, with proofreading taking the bulk of the time.</p>
My favorite records from 20202020-12-24T00:00:00Zhttps://danburzo.ro/favorite-records-2020/<p>This year has seen a ton of new releases from musicians both familiar and unknown-to-me. I haven't got around to giving a proper listen to many albums I had on my radar, but here's a sampler of what I liked, or found interesting, in 2020.</p>
<ul>
<li><a href="https://against-all-logic.bandcamp.com/album/2017-2019">Against All Logic — 2017-2019</a></li>
<li><a href="https://boomkat.com/products/a-forest-c29998d5-512c-465d-ab6a-20cad1eed549">Alva Noto — A Forest</a>, and <a href="https://boomkat.com/products/unieqav-remixes">Unieqav Remixes</a></li>
<li><a href="https://anoushkashankar.com/music/singles-eps/love-letters-ep">Anoushka Shankar — Love Letters</a></li>
<li><a href="https://www.theguardian.com/music/2020/mar/20/baxter-dury-the-night-chancers-review">Baxter Dury — The Night Chancers</a></li>
<li><a href="https://brendan-perry.bandcamp.com/album/songs-of-disenchantment-music-from-the-greek-underground">Brendan Perry — Songs of Disenchantment: Music from the Greek Underground</a></li>
<li><a href="https://bleep.com/release/213397-burial-four-tet-thom-yorke-her-revolution-his-rope">Burial, Four Tet & Thom Yorke — Her Revolution / His Rope</a></li>
<li><a href="https://caribouband.bandcamp.com/album/suddenly">Caribou — Suddenly</a></li>
<li><a href="https://unseenworlds.bandcamp.com/album/ganci-figli">Carl Stone — Ganci & Figli</a></li>
<li>★ <a href="https://www.deutschegrammophon.com/en/catalogue/products/parallels-shellac-reworks-beethoven-christian-loeffler-12169">Christian Löffler — Parallels: Shellac Reworks (Beethoven)</a></li>
<li><a href="https://www.azulmusic.com.br/produtos/ver/259/reminiscences-vol.-1.html">Clara Sverner — Reminiscences (Vol I & II)</a></li>
<li><a href="https://danielavery.bandcamp.com/album/love-light">Daniel Avery — Love + Light</a></li>
<li><a href="https://room40.bandcamp.com/album/apparition-paintings">David Toop — Apparition Paintings</a></li>
<li><a href="https://devendrabanhart.bandcamp.com/album/vast-ovoid">Devendra Banhart — Vast Ovoid</a></li>
<li><a href="https://dominikeulberg.bandcamp.com/album/mannigfaltig-remixes-pt-2">Dominik Eulberg — Mannigfaltig Remixes (Pt. 2)</a></li>
<li><a href="https://thou.bandcamp.com/album/may-our-chambers-be-full">Emma Ruth Rundle & Thou — May our Chambers be Full</a></li>
<li><a href="https://fabriziopaterlini.bandcamp.com/album/transitions-ii">Fabrizio Paterlini — Transitions II</a>, <a href="https://fabriziopaterlini.bandcamp.com/album/transitions-iii">Transitions III</a></li>
<li><a href="https://fauxreal.bandcamp.com/album/faux-real">Faux Real — Faux Real</a></li>
<li><a href="https://fourtet.bandcamp.com/album/sixteen-oceans">Four Tet — Sixteen Oceans</a></li>
<li><a href="https://www.theguardian.com/music/2020/feb/07/makaya-mccraven-gil-scott-heron-were-new-here-review-xl">Gill Scott-Heron — We're New Again: a Reimagining by Makaya McCraven</a></li>
<li><a href="https://gyda.bandcamp.com/album/epicycle-ii">Gyda Valtysdottir — Epicycle II</a></li>
<li><a href="https://haniarani.bandcamp.com/album/home">Hania Rani — Home</a></li>
<li><a href="https://italtek.bandcamp.com/album/outland">Ital Tek — Outland</a></li>
<li><a href="https://jesu.bandcamp.com/album/terminus">Jesu — Terminus</a></li>
<li><a href="https://jonsi.bandcamp.com/album/shiver">Jónsi — Exhale</a></li>
<li><a href="https://boomkat.com/products/the-falling-acts">Kastil — The Falling Acts</a></li>
<li><a href="https://keleketla.bandcamp.com/album/keleketla">Keleketla! — Keleketla!</a></li>
<li><a href="https://kellyleeowens.bandcamp.com/album/inner-song">Kelly Lee Owens — Inner Song</a></li>
<li><a href="https://kevinmorby.bandcamp.com/album/sundowner">Kevin Morby — Sundowner</a></li>
<li>★ <a href="https://khruangbin.bandcamp.com/album/mordechai">Khruangbin — Mordechai</a></li>
<li><a href="https://laurelhalo.bandcamp.com/album/possessed">Laurel Halo — Possessed</a></li>
<li><a href="https://lidopimienta.bandcamp.com/album/miss-colombia">Lido Pimienta — Miss Colombia</a></li>
<li><a href="https://music.apple.com/gb/album/go-recki-symphony-no-3-symphony-of-sorrowful-songs/1492342486">Lisa Gerrard & Genesis Orchestra, Conducted by Yordan Kamdzhalov — Górecki Symphony No. 3: Symphony of Sorrowful Songs</a></li>
<li><a href="https://theplaintive.bandcamp.com/album/the-plaintive">Locust – The Plaintive</a></li>
<li><a href="https://mattberninger.bandcamp.com/album/serpentine-prison">Matt Berninger — Serpentine Prison</a></li>
<li><a href="https://meitei.bandcamp.com/album/kofu">Meitei — Kofu</a></li>
<li><a href="https://thequietus.com/articles/28104-melt-yourself-down-100-yes-review">Melt Yourself Down — 100% Yes</a></li>
<li><a href="https://michellegurevich.bandcamp.com/album/ecstasy-in-the-shadow-of-ecstasy">Michelle Gurevich — Ecstasy in the Shadow of Ecstasy</a></li>
<li><a href="https://whitelabrecs.bandcamp.com/album/give-shape-to-space">Mikael Lind — Give Shape to Space</a></li>
<li><a href="https://mogwai.bandcamp.com/album/zerozerozero">Mogwai — ZEROZEROZERO</a></li>
<li>★ <a href="https://muzztheband.bandcamp.com/album/muzz">Muzz — Muzz</a></li>
<li><a href="https://www.deutschegrammophon.com/en/catalogue/products/l-i-t-a-n-i-e-s-nicholas-lens-nick-cave-12128">Nick Cave & Nicholas Lens — L.I.T.A.N.I.E.S.</a></li>
<li><a href="https://nilsfrahm.bandcamp.com/album/empty">Nils Frahm — Empty</a></li>
<li><a href="https://okaykaya.bandcamp.com/album/surviving-is-the-new-living">Okay Kaya — Surviving is the New Living</a></li>
<li><a href="https://somekindofpeace.com/">Ólafur Arnalds — Some Kind of Peace</a></li>
<li><a href="https://perfumegenius.bandcamp.com/album/set-my-heart-on-fire-immediately">Perfume Genius — Set My Heart on Fire Immediately</a></li>
<li><a href="https://music.apple.com/us/album/philip-glass-king-lear-feat-ruth-wilson/1498999090">Philip Glass ft. Ruth Wilson — King Lear</a></li>
<li>★ <a href="https://phoebebridgers.bandcamp.com/album/punisher">Phoebe Bridgers — Punisher</a></li>
<li><a href="https://robinguthrie.bandcamp.com/album/another-flower">Robin Guthrie & Harold Budd — Another Flower</a></li>
<li><a href="https://store.deutschegrammophon.com/p51-i0028948377725/roger-eno-brian-eno/mixing-colours/index.html">Roger Eno & Brian Eno — Mixing Colours</a></li>
<li><a href="https://romanfluegel.bandcamp.com/album/acid-test">Roman Flügel — Acid Test</a></li>
<li>★ <a href="https://rone-music.bandcamp.com/album/room-with-a-view">Rone — Room with a View</a></li>
<li><a href="https://sarahmarychadwick.bandcamp.com/album/please-daddy-2">Sarah Mary Chadwick — Please Daddy</a></li>
<li><a href="https://sevdaliza.bandcamp.com/album/shabrang">Sevdaliza — Shabrang</a></li>
<li><a href="https://store.sigurros.com/album/odins-raven-magic-2">Sigur Rós, Hilmar Örn Hilmarsson, Steindór Andersen, Páll Guðmundsson & Maria Huld Markan Sigfúsdóttir — Odin's Raven Magic</a></li>
<li>★ <a href="https://slowmeadow.bandcamp.com/album/by-the-ash-tree">Slow Meadow — By the Ash Tree</a></li>
<li><a href="https://en.wikipedia.org/wiki/Folklore_%28Taylor_Swift_album%29">Taylor Swift — Folklore</a></li>
<li><a href="https://tennispagan.bandcamp.com/album/eko">Tennis Pagan — EKO</a></li>
<li><a href="https://thaoandthegetdownstaydown.bandcamp.com/album/temple">Thao and the Get Down Stay Down — Temple</a></li>
<li><a href="https://duststoredigital.com/album/final-collected-vexations">The Black Dog — Final Collected Vexations</a></li>
<li><a href="https://modernrecordings.de/shelter/">Thomas Bartlett — Shelter</a></li>
<li><a href="https://tricky.bandcamp.com/album/fall-to-pieces">Tricky — Fall to Pieces</a></li>
<li><a href="https://hominidsounds.bandcamp.com/album/energy-is-forever">UKAEA — Energy is Forever</a></li>
<li><a href="https://7klassik.bandcamp.com/album/ambient-layers">Various Artists — Ambient Layers</a></li>
<li><a href="https://boomkat.com/products/uzelli-elektro-saz">Various Artists — Uzelli Elektro Saz</a></li>
<li><a href="https://whomadewho.bandcamp.com/album/synchronicity">WhoMadeWho — Synchronicity</a></li>
<li><a href="https://williambasinski.bandcamp.com/album/lamentations">William Basinski — Lamentations</a></li>
<li><a href="https://zebrakatz.bandcamp.com/album/less-is-moor">Zebra Katz — Less is Moor</a></li>
</ul>
<p>And a few individual tracks:</p>
<ul>
<li><a href="https://bbisland.bandcamp.com/track/the-rich-stuff">Ariel Sharratt & Mathias Kom — The Rich Stuff</a></li>
<li><a href="https://danielavery.bandcamp.com/album/dusting-for-smoke">Daniel Avery — Lone Swordsman</a></li>
<li><a href="https://www.youtube.com/watch?v=LBc8zZKTqaE">Dope Lemon — Streets of your Town</a></li>
<li><a href="https://vimeo.com/472686439">Doves — Carousels</a></li>
<li><a href="https://www.youtube.com/watch?v=bvsZBdo5pEk">Jon Hopkins — Dawn Chorus</a></li>
<li>Thomas Feiner — <a href="https://thomasfeiner.bandcamp.com/track/guide-for-the-perplexed">Guide for the Perplexed</a> and <a href="https://thomasfeiner.bandcamp.com/track/encounters-at-the-end-of-the-world">Encounters at the End of the World</a></li>
<li><a href="https://tindersticks.bandcamp.com/track/youll-have-to-scream-louder">Tindersticks — You'll Have to Scream Louder</a></li>
<li><a href="https://yasminehamdan.bandcamp.com/track/choubi">Yasmine Hamdan — Choubi</a> (2017)</li>
<li><a href="https://francisbebey.bandcamp.com/track/sanza-tristesse">Francis Bebey — Sanza Tristesse</a> (1982–1984)</li>
</ul>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>Lists from around the www:</p>
<ul>
<li><a href="https://thequietus.com/articles/29302-the-quietus-top-100-albums-of-2020-norman-records">The Quietus</a></li>
<li><a href="https://www.roughtrade.com/gb/albums-of-the-year/">Rough Trade</a></li>
<li><a href="https://bleep.com/top-10-albums-of-the-year-2020">Bleep</a></li>
<li><a href="https://boomkat.com/charts/boomkat-end-of-year-charts-2020">Boomkat</a></li>
</ul>
<p>R.I.P. Harold Budd (1936–2020)</p>
A little npm head-scratcher2020-11-20T00:00:00Zhttps://danburzo.ro/a-little-npm-headscratcher/<p>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.</p>
<h2>The setup</h2>
<p>A <a href="https://github.com/danburzo/percollate">JavaScript project I maintain</a> has the following file structure, abridged:</p>
<pre><code>src/
util/
slurp.js
cli-opts.js
test/
cli-opts.test.js
index.js
</code></pre>
<p>Two tools I normally use for JavaScript projects are <code>tape</code> for tests and <code>eslint</code> for linting, and these I ultimately ran as:</p>
<pre class="language-bash"><code class="language-bash">tape test/**/*.test.js<br />eslint <span class="token punctuation">{</span>src,test<span class="token punctuation">}</span>/**/*.js *.js</code></pre>
<p>...but they're actually stored as npm scripts in my <code>package.json</code> file:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"test"</span><span class="token operator">:</span> <span class="token string">"tape test/**/*.test.js"</span><span class="token punctuation">,</span><br /> <span class="token property">"lint"</span><span class="token operator">:</span> <span class="token string">"eslint {src,test}/**/*.js *.js"</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>...and then invoked with <code>npm run test</code> and <code>npm run lint</code> respectively.</p>
<p>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.</p>
<p>Then one code change, which passed the pre-commit hooks, suddenly blew up the GitHub action: <code>npm run lint</code> had found two linting errors in the <code>src/cli-opts.js</code> file.</p>
<p>Huh. I fire up my terminal, on which I've been running the zsh shell for the last few years, and execute <code>npm run lint</code>, as one does.</p>
<p>No errors. What's going on?</p>
<p>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.</p>
<h2>The explanation</h2>
<p>What's going on is shells.</p>
<p>Remember I casually mentioned I run zsh? It turns out this matters. Bringing up the <a href="https://docs.npmjs.com/cli/v6/commands/npm-run-script">manual for <code>npm run</code></a> revealed this piece of hitherto unknown information:</p>
<blockquote>
<p>The actual shell your script is run within is platform dependent. By default, on Unix-like systems it is the <code>/bin/sh</code> command, on Windows it is the <code>cmd.exe</code>. The actual shell referred to by <code>/bin/sh</code> also depends on the system.</p>
</blockquote>
<p>Wait a second, you mean to tell me I've been running npm scripts with <code>GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)</code> all along?!</p>
<p>One aspect for which the choice of shell is important is that different shells have different <a href="https://en.wikipedia.org/wiki/Glob_%28programming%29">glob</a> expansion rules.</p>
<p>zsh supports <a href="http://zsh.sourceforge.net/Doc/Release/Expansion.html#Recursive-Globbing">recursive expansion</a> of the <code>**/</code> pattern, so that <code>src/**/*.js</code> matches both <code>src/util/slurp.js</code> and <code>src/cli-opts.js</code> while bash 3.2 only matches the former. It's only in version 4 that <a href="https://www.linuxjournal.com/content/globstar-new-bash-globbing-option">bash gets a <code>globstar</code> option</a> that allows recursive expansion, and even that might not be enabled by default.</p>
<p>Furthermore, when a glob pattern has no matching files, zsh throws an error. By default, bash outputs the glob pattern unchanged.</p>
<p>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:</p>
<pre class="language-bash"><code class="language-bash">src/<br /> util/<br /> slurp.js<br /> cli-opts.js<br />test/<br /> cli-opts.test.js<br />index.js<br /><br /><span class="token builtin class-name">echo</span> test/**/*.test.js<br /><span class="token comment"># zsh:</span><br />test/cli-opts.test.js<br /><span class="token comment"># bash 3.2:</span><br />test/**/*.test.js<br /><br /><span class="token builtin class-name">echo</span> <span class="token punctuation">{</span>src,test<span class="token punctuation">}</span>/**/*.test.js *.js<br /><span class="token comment"># zsh:</span><br />src/util/slurp.js<br />src/cli-opts.js<br />index.js<br />test/cli-opts.test.js<br /><br /><span class="token comment"># bash 3.2:</span><br />src/util/slurp.js<br />index.js<br />test/**/*.test.js</code></pre>
<p>Now, if we look at <code>tape</code> and <code>eslint</code>'s respective <code>package.json</code> files, we'll see both use the <a href="https://github.com/isaacs/node-glob">glob</a> package, which lets them accept glob patterns as operands, and to support the <code>**</code> globstar pattern in their expansion.</p>
<p>Bash's behavior on globs it was unable to match, that is to forward them unchanged as operands to the <code>tape</code> and <code>eslint</code> commands, is (counterintuitively!) the key thing that held the whole charade together, making bash work almost like zsh.</p>
<p>But not entirely. If you examine the expansions above closely, you may notice that in bash, we're not linting one set of files: anything directly under the <code>src/</code> folder. The linting command had quietly broke the moment I introduced the <code>src/util</code> subfolder, for reasons I'll leave as an exercise to the reader :-).</p>
<p>This subtle detail is why, for the most part, I hadn'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 <code>src/cli-opts.js</code> was caught by GitHub Actions, but not locally, that the jig was up.</p>
<p>Why <em>did</em> the <code>lint</code> command work in GitHub Actions? I have not had the energy to look into it, but my guess is the particular Ubuntu image I'm using may have bash version 4 or newer as its <code>/bin/sh</code>, with the <code>globstar</code> option enabled.</p>
<h2>The solution</h2>
<p>The solution, as always, is to quote glob patterns to prevent their expansion in the shell, and have them delivered intact to the <code>tape</code> and <code>eslint</code> commands, which will in turn expand them consistently regardless of the particular shell they're running in.</p>
<p><strong>package.json</strong></p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"test"</span><span class="token operator">:</span> <span class="token string">"tape 'test/**/*.test.js'"</span><span class="token punctuation">,</span><br /> <span class="token property">"lint"</span><span class="token operator">:</span> <span class="token string">"eslint '{src,test}/**/*.js' '*.js'"</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>I hope you found this write-up illuminating!</p>
Make PNG font samples with ImageMagick2020-04-27T00:00:00Zhttps://danburzo.ro/imagemagick-font-samples/<p>In this article we'll be poking around <a href="https://imagemagick.org/">ImageMagick</a> from the command-line to get it to make great looking PNG previews of sample text typeset in various fonts, like the one below.</p>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/font-preview.png" height="101" width="153" alt="The final result" />
</figure>
<p>As always with ImageMagick and other tinker-friendly tools, we'll accidentally learn a few other things along the way.</p>
<blockquote>
<p><strong>Note:</strong> I'm using the latest version of ImageMagick which, at the time of writing, is <code>7.0.10</code>. ImageMagick 7 introduces a single <code>magick</code> command for everything; replace it with <code>convert</code> in the examples below in case you're using version 6 or earlier.</p>
</blockquote>
<h2>Writing one line of text</h2>
<p><em>The quick brown fox jumps over the lazy dog</em> and <em>The five boxing wizards jump quickly</em> are two well-known <a href="https://en.wikipedia.org/wiki/Pangram">pangrams</a> — compact phrases that use most of the letters from the Latin alphabet, an aspect which makes them excellent choices for previewing typefaces.</p>
<p>As our first quest, let's write the sentence <em>The five boxing wizards jump quickly</em> on a piece of PNG.</p>
<p>In ImageMagick, <a href="https://imagemagick.org/script/command-line-options.php#draw">the <code>-draw</code> option</a> allows us to paint all sorts of things onto an image — pixels, shapes, and text:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 1000x200 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">"text 50,50 'The five boxing wizards jump quickly.'"</span> <span class="token punctuation">\</span><br />sample.png</code></pre>
<p>In the command above we set up a transparent canvas (<code>canvas:none</code>) of <code>1000×200</code> pixels (the <code>-size</code> option); using a font size of 48 points we then write our phrase starting at the <code>(50,50)</code> coordinate. The result, shown below with a red border to discern the canvas bounds, is already promising:</p>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/sample.png" height="101" width="501" />
<figcaption>Our first line of text.</figcaption>
</figure>
<p>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't cut it, especially if we aspire to run the command automatically for lots and lots of fonts. This is where <a href="https://imagemagick.org/script/command-line-options.php#gravity">the <code>-gravity</code> option</a> comes in handy: with it, we can align the canvas and the text by their matching side. For example, <code>-gravity West</code> will place the text's left-hand side against the canvas's left-hand side:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 1000x200 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-gravity</span> West <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">"text 0,0 'The five boxing wizards jump quickly.'"</span> <span class="token punctuation">\</span><br />sample-centered.png</code></pre>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/sample-centered.png" height="101" width="501" />
<figcaption>The same text, now centered vertically.</figcaption>
</figure>
<p>Much better! Notice we've replaced the <code>(50,50)</code> coordinate pair with <code>(0,0)</code>; we don't want the text to be offset at all from the edge of the image.</p>
<p>Alternatively, instead of trying to position the text on the canvas, we can make the canvas fit snuggly around the text with the <code>-trim</code> option:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 1000x200 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-gravity</span> Center <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">"text 0,0 'The five boxing wizards jump quickly.'"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-trim</span> <span class="token punctuation">\</span><br />sample-trimmed.png</code></pre>
<p>For best results, place the text smack in the middle of the canvas (<code>-gravity Center</code>), 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't hurt to start with considerable padding.</p>
<h2>Using specific fonts</h2>
<p>Up until now we've been drawing text with some Helvetica-or-similar font. But the whole point of the exercise was to make PNG previews for <em>fonts</em>, plural. The <a href="https://imagemagick.org/script/command-line-options.php#font"><code>-font</code> option</a> takes a path to a font file (<code>.ttf</code>, <code>.otf</code>, etc.) to use for drawing all the text:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 1000x200 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-font</span> ./MyFont.otf<br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-gravity</span> Center <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">"text 0,0 'The five boxing wizards jump quickly.'"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-trim</span> <span class="token punctuation">\</span><br />MyFont-sample.png</code></pre>
<blockquote>
<p>The <code>-font</code> 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're bound to the list you see when you run <code>magick -list font</code>; what it contains depends on your operating system. On macOS, you might find <a href="https://github.com/testdouble/imagemagick-macos-font-setup">this script</a> by Justin Searls useful for making ImageMagick aware of system fonts.</p>
</blockquote>
<p>What's better than using a custom font? Using a whole bunch of custom fonts! In a folder full of <code>.otf</code> font files, we can generate PNG samples for them in bulk using a for-loop:</p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">for</span> <span class="token for-or-select variable">font</span> <span class="token keyword">in</span> *.otf<span class="token punctuation">;</span> <span class="token keyword">do</span> <span class="token punctuation">\</span><br />magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 1000x200 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-font</span> ./<span class="token variable">$font</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-gravity</span> Center <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">"text 0,0 'The five boxing wizards jump quickly.'"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-trim</span> <span class="token punctuation">\</span><br /><span class="token string">"<span class="token variable">$font</span>-sample.png"</span><span class="token punctuation">;</span> <span class="token keyword">done</span></code></pre>
<p>The <code>$font</code> variable holding the path to the current font shows up twice in the command: as <code>-font ./$font</code> for writing text, and as part of the output filename, <code>$font-sample.png</code>.</p>
<p>This gets us nicely named, nicely trimmed, previews:</p>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/collated-samples.png" height="322" width="422" />
<figcaption>Samples in various fonts.</figcaption>
</figure>
<h2>Making a GIF out of the images</h2>
<p>I thought <code>ffmpeg</code> is a good tool to make an animated GIF that uses the PNGs as frames, but <a href="https://stackoverflow.com/questions/14676119/imagemagick-and-transparent-background-for-animated-gif#14676207">this Stack Overflow answer</a> showed me a quicker solution with ImageMagick's <code>convert</code> utility:</p>
<pre class="language-bash"><code class="language-bash">magick convert <span class="token punctuation">\</span><br /> <span class="token parameter variable">-delay</span> <span class="token number">0</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-dispose</span> Previous <span class="token punctuation">\</span><br />*.png fonts.gif</code></pre>
<p>The <code>-delay</code> option sets how much each frame in the animation lasts, with <code>0</code> having the frames play as fast as possible. The <code>-dispose Previous</code> option clears the canvas after each frame, instead of overlaying frames on top of each other.</p>
<figure>
<img style="border: 1px solid" src="https://danburzo.ro/img/imagemagick-font-previews/fonts.gif" height="100" width="500" />
<figcaption>All the samples as one animated GIF.</figcaption>
</figure>
<p>A beautiful animated GIF where... the canvas is <code>1000×200</code> pixels in size and all the texts are centered?! As far as we know, every individual sample is trimmed to a different size!</p>
<p>Turns out that ImageMagick tags images it produces with metadata it can, and will, then use if the image file is further processed. <a href="https://imagemagick.org/script/identify.php">The <code>identify</code> command</a> lists out the metadata:</p>
<pre class="language-bash"><code class="language-bash">magick identify <span class="token parameter variable">-verbose</span> MyFont-sample.png</code></pre>
<p>Here's an abridged version of the output:</p>
<pre><code>Image: MyFont-sample.png
Format: PNG (Portable Network Graphics)
(...)
Geometry: 738x45+0+0
(...)
Page geometry: 1000x200+130+76
Origin geometry: +130+76
</code></pre>
<p>It now becomes clear that the <code>-trim</code> option we had used to generate the PNGs stores the position of the trimmed area on the original canvas as the <em>Page geometry</em> metadata. Flashback to <a href="https://imagemagick.org/script/command-line-options.php#trim">all the words in the docs</a> and the bit about <code>+repage</code> starts to make sense:</p>
<blockquote>
<p>The page or virtual canvas information of the image is preserved allowing you to extract the result of the <code>-trim</code> operation from the image. Use a <code>+repage</code> to remove the virtual canvas page information if it is unwanted.</p>
</blockquote>
<p>To remove the extraneous <em>Page geometry</em> metadata, the adjusted command is:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 1000x200 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-font</span> ./MyFont.otf <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-gravity</span> Center <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">"text 0,0 'The five boxing wizards jump quickly.'"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-trim</span> <span class="token punctuation">\</span><br /> +repage <span class="token punctuation">\</span><br />MyFont-sample.png</code></pre>
<p>Sure enough, the same(-ish) command to build the GIF:</p>
<pre class="language-bash"><code class="language-bash">magick convert <span class="token punctuation">\</span><br /> <span class="token parameter variable">-delay</span> <span class="token number">0</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-dispose</span> Previous <span class="token punctuation">\</span><br /> <span class="token parameter variable">-layers</span> trim-bounds <span class="token punctuation">\</span><br />*.png fonts.gif</code></pre>
<p>Now does what we'd expected it to do in the first place:</p>
<figure>
<img style="border: 1px solid" src="https://danburzo.ro/img/imagemagick-font-previews/fonts-adjusted.gif" height="37" width="500" />
<figcaption>All the samples as one animated GIF, properly sized and aligned.</figcaption>
</figure>
<p>Not that it's in any way better, but, you know, for the sake of completeness. And yes, I cheated a smidge. I added in the <code>-layers trim-bounds</code> option, <em>on the house</em>, 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.</p>
<p>After this brief foray into GIF-making, let's get back to the subject at hand.</p>
<h2>Writing more than one line of text</h2>
<p>ImageMagick doesn't have any built-in mechanisms to lay out text on multiple lines, so we have to do it manually, line by line. The <code>-draw</code> routine becomes:</p>
<pre class="language-bash"><code class="language-bash"><span class="token parameter variable">-draw</span> <span class="token string">" \<br /> text 50,64 'The five boxing' \<br /> text 50,128 'wizards jump' \<br /> text 50,192 'quickly.'"</span> <span class="token punctuation">\</span></code></pre>
<p>Here we devise a line height of 64 points, and place the lines of text at <code>64×1</code>, <code>64×2</code>, and so on. On the horizontal axis, we're not starting the text directly at the edge of the image, because the flourishes on <a href="https://fonts.google.com/specimen/Monsieur+La+Doulaise">some of the more decorated typefaces</a> may get cut off. With a 50-point offset, we give the font ample space to do its thing, and we'll get rid of the excess later, using the <code>-trim</code> option.</p>
<p>The full command is presented below:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 640x320 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-font</span> ./MyFont.otf <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">" \<br /> text 50,64 'The five boxing' \<br /> text 50,128 'wizards jump' \<br /> text 50,192 'quickly.'"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-trim</span> <span class="token punctuation">\</span><br /> +repage <span class="token punctuation">\</span><br />MyFont-sample.png</code></pre>
<p>And produces images such as this one:</p>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/myfont-sample.png" height="173" width="273" />
<figcaption>A multiline font sample.</figcaption>
</figure>
<p>But when put side by side, we notice the baselines don't align across images.</p>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/side-by-side.png" height="204" width="1491" />
<figcaption>Font samples side by side.</figcaption>
</figure>
<p>If we put the trimmed images side by side, we can tell the baselines don'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.</p>
<p>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:</p>
<pre class="language-bash"><code class="language-bash">magick <span class="token punctuation">\</span><br /> <span class="token parameter variable">-size</span> 640x320 <span class="token punctuation">\</span><br /> canvas:none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-font</span> ./MyFont.otf <span class="token punctuation">\</span><br /> <span class="token parameter variable">-pointsize</span> <span class="token number">48</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-background</span> none <span class="token punctuation">\</span><br /> <span class="token parameter variable">-splice</span> 0x1 <span class="token punctuation">\</span><br /> <span class="token parameter variable">-draw</span> <span class="token string">" \<br /> text 50,64 'The five boxing' \<br /> text 50,128 'wizards jump' \<br /> text 50,192 'quickly.' \<br /> point 50,0"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-trim</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-chop</span> 0x1 <span class="token punctuation">\</span><br /> +repage <span class="token punctuation">\</span><br />MyFont-sample.png</code></pre>
<p>Firstly, <code>-splice 0x1</code> adds a <em>transparent</em> (by virtue of the previous <code>-background none</code> declaration) pixel row at the top of the image. Then <code>-draw point 50,0</code> places a solid black pixel on that top row, which causes the subsequent <code>-trim</code> option to only crop transparent pixels along the other three edges. Finally, <code>-chop 0x1</code> removes the row we added earlier. This makes the text in all images line up nicely.</p>
<figure>
<img src="https://danburzo.ro/img/imagemagick-font-previews/side-by-side-aligned.png" height="206" width="1491" />
<figcaption>Font samples side by side, properly aligned.</figcaption>
</figure>
<p>That's it! That's how you make PNG font samples with ImageMagick.</p>
My favorite records from 20192020-01-03T00:00:00Zhttps://danburzo.ro/favorite-records-2019/<p><em>As you can notice, 2019 had a bit of a work-in-progress vibe to it.</em></p>
<hr />
<p>Top records:</p>
<ul>
<li>Sharon Van Etten — Remind Me Tomorrow</li>
<li>The National — I Am Easy To Find</li>
<li>Vanessa Wagner — Inland / Inland Versions</li>
<li><a href="https://jacquesgreene.bandcamp.com/album/dawn-chorus">Jacques Greene — Dawn Chorus</a></li>
</ul>
<p>Other records of note:</p>
<ul>
<li>Abul Mogard</li>
<li>Acid Arab — Jdid</li>
<li>Aldous Harding — Designer</li>
<li>Alex Cameron — Miami Memory</li>
<li>Altin Gün — Gece</li>
<li>Angel Olsen — All Mirrors</li>
<li>Bedouine — Bird Songs of a Killjoy</li>
<li>Beirut — Gallipoli</li>
<li>Blanck Mass — Animated Violence Mild</li>
<li>Blawan — Many Many Pings</li>
<li>Devendra Banhart — Ma</li>
<li>Fennesz — Agora</li>
<li>Floating Points — Crush</li>
<li>Helado Negro — This Is How You Smile</li>
<li>Holly Herndon — Proto</li>
<li>Jay-Jay Johanson — Kings Cross</li>
<li>Kangding Ray — Azores EP</li>
<li>Kastil</li>
<li>Kazu — Adult Baby</li>
<li>Lana del Rey — NFR!</li>
<li>Nick Cave & the Bad Seeds — Ghosteen</li>
<li>Nikveh</li>
<li>Rhye — Spirit</li>
<li>Slow Meadow — Happy Occident</li>
<li>Sun O)))) — Life Metal</li>
<li>Thom Yorke — Anima</li>
<li>Tigran Hamasyan — They Say Everything Stays The Same</li>
<li>Tool</li>
</ul>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
Prevent history navigation on horizontally-scrolling elements with CSS2019-10-16T00:00:00Zhttps://danburzo.ro/css-overscroll-behavior/<p>Behold a horizontally-scrolling element:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.reel</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span><br /> <span class="token property">overflow-x</span><span class="token punctuation">:</span> scroll<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This pattern is particularly useful on mobile devices as <a href="https://hankchizljaw.com/wrote/progressive-overflow-management-with-a-scroll-track-utility/">an alternative to stacking</a> 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.</p>
<p>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 (there's <a href="https://twitter.com/danburzo/status/1184375415235862528">probably a name for the phenomenon</a>).</p>
<p>To prevent the navigation, we can use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior"><code>overscroll-behavior</code></a> CSS property. A value of <code>none</code> or <code>contain</code> will make sure the excess scroll does not ripple to the container's ancestors and, ultimately, the page:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.reel</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span><br /> <span class="token property">overflow-x</span><span class="token punctuation">:</span> scroll<span class="token punctuation">;</span><br /> <span class="token property">overscroll-behavior-x</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Here's <a href="https://danburzo.ro/demos/overscroll-behavior.html">a demo highlighting the difference</a>.</p>
<p>At the time of writing, this property is supported in Firefox <sup><a href="https://danburzo.ro/css-overscroll-behavior/#fn-1">1</a></sup>, Chrome <sup><a href="https://danburzo.ro/css-overscroll-behavior/#fn-2">2</a></sup>, and Edge, so most desktop users will benefit from the enhanced behavior.</p>
<p><strong>For extra credit</strong> also opt into smooth, accelerated scrolling in mobile Safari with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-overflow-scrolling"><code>-webkit-overflow-scrolling: touch</code></a>. While iOS / iPadOS 13 <a href="https://developer.apple.com/documentation/safari_release_notes/safari_13_release_notes">makes this the default</a>, it's still a nice, ahem, <em>touch</em> for older versions.</p>
<h2>Updates</h2>
<p><strong>March 26, 2022:</strong> The <code>overscroll-behavior</code> CSS property is now available for testing in <a href="https://webkit.org/blog/12522/release-notes-for-safari-technology-preview-142/">Safari Technology Preview 142</a> (yay!)</p>
<p><strong>August 7, 2022:</strong> But history navigation is explicitly allowed despite <code>overscroll-behavior</code> (sad tromobone) [<a href="https://bugs.webkit.org/show_bug.cgi?id=240183">WebKit#240183</a>].</p>
<hr />
<p><sup id="fn-1">1</sup> There's a <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1595791">small issue in Firefox</a> with the first swipe.</p>
<p><sup id="fn-2">2</sup> I stumbled upon a peculiar combination <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1010454#c_ts1570106892">that made Chrome ignore the declaration</a>.</p>
Prevent the need to zoom into inputs with CSS2019-10-08T00:00:00Zhttps://danburzo.ro/css-safari-zoom-inputs/<p>Adrian Roselli describes <a href="https://adrianroselli.com/2019/09/under-engineered-text-boxen.html">a quick, effective way</a> to make HTML 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 <code>font-size</code> equivalent to <code>1em</code>:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">textarea,<br />input</span> <span class="token punctuation">{</span><br /> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>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. It happens whenever an input has its text smaller than <code>16px</code>, and it's a sensible thing to do. However, in some cases, it's purely accidental: you get this <em>barely zooming into an input</em> because it happens to have a computed font size of <code>15px</code>, or <code>14px</code>, as inherited from its parent.</p>
<p>To proactively fix this, and spare the user a useless interaction, I got into the habit of throwing in an extra declaration:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">button,<br />select,<br />textarea,<br />input</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>16px<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>In browsers supporting <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max">the <code>max()</code> function</a> — including, thankfully, Safari — it prevents the inherited font size of inputs from going below <code>16px</code>, and avoids the zoom behavior.</p>
<p>Nice and easy!</p>
<hr />
<p>Some notes:</p>
<ul>
<li>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 <em>zoom into input</em> thing.</li>
<li>One thing that's still prevalent is using <code>maximum-scale=1.0</code>, or <code>user-scalable=no</code> in the <code>viewport</code> meta tag. Despite Safari ignoring them since iOS 10 to allow users to pinch-zoom regardless, they're <a href="https://a11yproject.com/posts/never-use-maximum-scale/">still bad for accesibility</a>.</li>
</ul>
Making sense of units in CSS Media Queries (in the year 2019)2019-09-29T00:00:00Zhttps://danburzo.ro/media-query-units/<p>As browsers are starting to ship parts of the <a href="https://drafts.csswg.org/mediaqueries-5/">Media Queries Level 5</a> spec, recent discussion on CSS media queries understandably revolves around using these new features to better adjust web pages to users' needs and preferences. In 2019, the old-school queries <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries">published in 2012</a> as a W3C Recommendation, and near-universally supported across browsers, seem to have been exhaustively dissected and discussed.</p>
<p>Still, I felt some aspects of how they work continued to elude me. In this article I set out to clarify them.</p>
<hr />
<p>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.</p>
<p>There are <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Media_features">many features</a> we can test. I'm going to look at a tiny slice: the <code>width</code> and <code>height</code> media features, with their associated <code>min-</code> and <code>max-</code> queries. They tell us things about the size of the space allocated for the web page.</p>
<p>A media query inside CSS uses the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media"><code>@media</code> rule</a>:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/*<br /> Makes the page red whenever there are <br /> at least 400px available for it in the browser.<br /> */</span><br /><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 400px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>When we qualify the <code>width</code> feature with the <code>min-</code> prefix, the query reads as <em>at least</em>, while the <code>max-</code> stands for <em>at most</em>.</p>
<blockquote>
<p>The Media Queries Level 4 specification introduces <a href="https://www.w3.org/TR/mediaqueries-4/#mq-range-context">a clearer syntax</a>: <code>(width >= 400px)</code> instead of <code>(min-width: 400px)</code>. Since it's a relatively new addition, it's not a good replacement for the classic syntax yet.</p>
</blockquote>
<h2>Are dimension queries useful?</h2>
<p>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 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design#Media_Queries">pivotal to propelling Responsive Web Design</a> into ubiquity.</p>
<p>With new, more powerful, ways of expressing layout such as <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout">Flexible Box</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout">Grid</a>, CSS gains alternatives to media queries for responsive layouts. <a href="https://every-layout.dev/">Every Layout</a> by Heydon Pickering and Andy Bell is an excellent resource to get a feel for using <code>flex</code> and <code>grid</code> properties, often combined with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/calc"><code>calc()</code></a>, for common patterns normally solved by media queries. CSS is also getting the <code>min()</code>, <code>max()</code>, and <code>clamp()</code> <a href="https://drafts.csswg.org/css-values-4/#comp-func">comparison functions</a>, which extend <code>min-width</code>/<code>max-width</code>/<code>min-height</code>/<code>max-height</code> to all properties, and promise to further erode dimension queries' territory.</p>
<p>Although these recent developments don't make dimension queries obsolete, they relegate them to an <a href="https://www.smashingmagazine.com/2018/02/media-queries-responsive-design-2018/">auxiliary role in responsive design</a>. That's a good thing! Media queries are <em>coarse</em>, and best suited to make top-level adjustments based on top-level constraints.</p>
<p>Dimension queries are not strictly a CSS thing, eiher. In HTML, width queries also show up in the <code>sizes</code> attribute on <code><img></code> and <code><source></code> elements to enable <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">responsive images</a>.</p>
<p>All in all, it seems we can't Marie Kondo them out of our web design toolbox just yet, so let's see how we can use dimension queries efficiently. From here on, I'm going to call them just <em>media queries</em>, since they're the only ones discussed.</p>
<h2>Units in media queries</h2>
<p>How does our choice of CSS units in media queries influence our design, and our users' ability to express preferences for their experience?</p>
<hr />
<p>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 <code>px</code> unit, short for <em>pixel</em>. For the sake of simplicity let's gloss over, for a short while, how CSS pixels are not the same thing as device pixels. They <em>sure feel</em>, at first brush, like they're the same.</p>
<p>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've designed it.</p>
<p>But <code>px</code> 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.</p>
<p>CSS gives us font-relative CSS units — <code>em</code>, <code>rem</code>, 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 <em>relate</em> to exactly?</p>
<p>According to the spec, they relate to the <em>initial</em> value of font properties. For <code>em</code> and <code>rem</code>, the relevant property is <code>font-size</code>, which most browsers initially set to <code>16px</code>.</p>
<p>As such, changing the font size of the <code>html</code> element:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>...detaches the meaning of <code>rem</code>s in your styles from the meaning of <code>rem</code>s in media queries: <code>1rem</code> in styles is now equivalent to <code>20px</code>, while in media queries <code>1rem</code>, and <code>1em</code> for that matter, is still <code>16px</code>.</p>
<p>Why would the spec mandate this in the first place?</p>
<p>The CSS Working Group explain in a FAQ entry that <a href="https://wiki.csswg.org/FAQ#selectors-that-depend-on-layout">selectors can't depend on layout</a>. If <code>rem</code> / <code>em</code> media queries depended on the <code>font-size</code> of the <code>html</code> element, you could create an infinite loop:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 60rem<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* <br /> Setting this invalidates <br /> the media query selector <br /> that triggered this style.<br /> */</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 10rem<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>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 —</p>
<h3>The trouble with Safari</h3>
<p>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.</p>
<p>Safari follows the spec for most relative units: <code>1em</code> in media queries is <code>16px</code> regardless of the font size on the <code>html</code> element. But it scales <code>rem</code>s in particular in accordance to the <code>html</code> element (<a href="https://bugs.webkit.org/show_bug.cgi?id=156684">WebKit#156684</a>, fixed in <a href="https://webkit.org/blog/12156/release-notes-for-safari-technology-preview-137/">Safari TP 137</a>). Since this breaks the "no layout-dependent selectors" CSS rule, we can actually <a href="https://danburzo.github.io/browser-summary/safari">witness the infinite loop</a> described above.</p>
<p>Got us there, Safari! But since we can use <code>em</code> and <code>rem</code> interchangeably, as they relate to the same thing in any spec-respecting browser, we just pick the not-broken one.</p>
<blockquote>
<p>If we stick to <code>em</code> in media queries, we bring Safari's behavior in line with the other browsers.</p>
</blockquote>
<h2>User preferences</h2>
<p>Users have a few ways of adjusting their experience of a web page. Let's go through them, one by one, to see their impact on our choice of units for media queries.</p>
<h3>Zooming in</h3>
<p>Remember the whole <em>pixels are not pixels</em> thing we avoided earlier? It becomes key to how zoom works. On screens, relative units resolve to <code>px</code>. These are <em>CSS pixels</em>, distinct from physical pixels on the device. They map to physical pixels based on the device's pixel density. You can read more about it in <a href="https://hacks.mozilla.org/2013/09/css-length-explained/">CSS Length Explained</a>, but what matters is there's a certain <em>ratio</em> between what constitutes a pixel in CSS and on the device, so that you can experience one CSS pixel roughly the same across devices.</p>
<p>When you change the zoom level, modern browsers will <strong>tweak the ratio between CSS pixels and device pixels</strong>. <em>1px</em> 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.</p>
<p><code>1rem</code> is still <code>16px</code> when you zoom in, but now there are fewer (CSS) pixels available to your page. This reflects in the media queries: <code>min-width</code> has a lower threshold — fewer pixels, fewer <code>rem</code>s, fewer <code>em</code>s. Suddenly —</p>
<h4>The trouble with Safari (again)</h4>
<p>Safari on macOS has a bug where <code>em</code> and <code>rem</code> units in media queries get the browser's zoom level factored in (<a href="https://bugs.webkit.org/show_bug.cgi?id=156687">WebKit#156687</a>).</p>
<p>As you zoom in, <code>1rem</code> becomes <code>20px</code>, and then <code>28px</code> 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 <code>em</code> and <code>rem</code>s.</p>
<p>With iOS 13, and the new iPadOS, Safari also introduced zoom controls for mobile users. They work <em>much</em> better to adjust the layout than the <em>Request Desktop/Mobile Website</em> feature, which does nothing on websites built on responsive design principles. They're part of the reason I wanted to learn more about media queries and zooming.</p>
<p>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.)</p>
<p>Since it'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 <code>em</code>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.</p>
<p><strong>Fun with <code>vw</code>.</strong> Safari on macOS applies the zoom level to all relative units, and that includes <code>vw</code>. Yes, <code>min-width</code> and <code>max-width</code> don't always match <code>100vw</code>. Why, that means we can use media queries to <a href="https://danburzo.github.io/browser-summary/safari-zoom">detect the zoom level</a>!</p>
<p>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 <code>vw</code> media queries are not affected by the <html> element's font size the way <code>rem</code>s are, we can, if that helps in any way, go ahead and adjust it:</html></p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 133.33vw<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token comment">/* the zoom level in Safari is at most 75% */</span><br /> <span class="token selector">html</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Something smaller than usual */</span><br /> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>In the example above, <code>133.33</code> comes from dividing 100 with the maximum zoom level we want to match, in our case <code>0.75</code> (75%).</p>
<hr />
<p>Adjusting the zoom level is common, as it's readily available in menus and via keyboard shortcuts, and browsers do a good job of honoring it.</p>
<h3>Changing the font settings</h3>
<p>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:</p>
<h4>Changing the default size</h4>
<p>So far we haven't really examined an assumption we made earlier: that the initial font size in browsers is <code>16px</code>.</p>
<p>According to <a href="https://medium.com/@vamptvo/pixels-vs-ems-users-do-change-font-size-5cfb20831773">research by Evan Minto</a>, around 3% of users navigate the web at other sizes, either because browsers themselves have a default size other than <code>16px</code>, or the user has changed the default.</p>
<p>The distinction doesn'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 <code>html</code> element's font size. It's just not always <code>16px</code>.</p>
<p>At this point, it's worth noting the consequences of absolute units for the <code>html</code> font size. Using <code>html { font-size: 12px; }</code> forces a size on the page that <em>outright</em> ignores the user preference.</p>
<p>In addition to being insensitive to the user, we further (and unpredictably) detach the notion of <code>1em</code> in media queries — which, remember, are still based on that preference — from what's actually displayed on the page.</p>
<p>Rather, think about it in terms of:</p>
<blockquote>
<p><em>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?</em></p>
</blockquote>
<p>...and then use font-relative units for the <code>html</code> font size to express it. These units <em>tweak</em> the user preference rather than dismiss it altogether.</p>
<h4>Setting a minimum font size</h4>
<p>As a supplement to the default font size, browsers can also impose a minimum font size.</p>
<p>This does not normally¹ affect the initial font size. The font-relative media queries and <code>font-size</code> 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.</p>
<p>When the minimum font size kicks in, browsers behave slightly differently. In Firefox, style declarations other than <code>font-size</code> using <code>em</code> and <code>rem</code> 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 <code>1em</code> around a text will remain proportional if the size is increased as a result of the minimum font size.</p>
<hr />
<p>¹ Safari comes with a single setting called <em>never use font sizes smaller than X</em>. It gets factored into the initial font size, affecting media queries in ways I can't quite make heads and tails of, so... it's left as an exercise to the reader? :-)</p>
<h3>Text-only zoom</h3>
<p>Firefox has a <em>Zoom text only</em> feature which alters the way zoom works. It disables the scaling of <em>CSS pixels</em>, and instead factors the zoom level into the initial font size.</p>
<p>That means that media queries using font-relative units (<code>em</code>, <code>rem</code>) get a new basis, matching the initial font size of the <code><html></code> element.</p>
<p>To really drive the feature home, and make it work as expected on pages which might use an <em>absolute</em> font size on the <code><html></code> element, it also factors the zoom level into the <code>font-size</code> computed value.</p>
<p>Everything works splendidly. The big losers here are <code>px</code> queries. When you zoom in, the content gets bigger and bigger, and nothing changes in queryland, since there's no scaling of <em>CSS pixels</em>.</p>
<p>One less reason to ever use them!</p>
<h2>Conclusion</h2>
<p>Some takeaways from this foray into media queries:</p>
<p><code>px</code>-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's preferences about font size, and can't handle Firefox's text-only zoom.</p>
<p>When it comes to font-relative CSS units, your best bet for predictable cross-browser behavior is to use <code>em</code> in media queries. It avoids the problem with <code>rem</code> in desktop Safari, but otherwise they're interchangeable. Zoom out of the page in macOS Safari to check that it does not break.</p>
<p>When adjusting the font size on the <code>html</code> element, use font-relative units to respect the user's preferences. And keep in mind that by changing it from the default, you're <em>slightly</em> shifting the meaning of <code>1rem</code> in styles vs. <code>1rem</code> / <code>1em</code> in media queries.</p>
<p>It'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.</p>
<p><em>Thanks to Simon for corrections & guidance in navigating the W3C specs.</em></p>
<hr />
<h2>Appendix: Deprecated media queries</h2>
<p><a href="https://www.w3.org/TR/2017/CR-mediaqueries-4-20170905/#mf-deprecated">CSS Media Queries 4</a> deprecates the use of <code>device-width</code>, <code>device-height</code>, and <code>device-aspect-ratio</code>, which previously referred to physical pixels. Instead, browsers should <a href="https://drafts.csswg.org/cssom-view-1/#web-exposed-screen-area">start reporting them in CSS pixels</a>, which Firefox has already started doing (Edge seems to do so as well, but I can't tell exactly in Browserstack). Safari and Chrome continue to report physical pixels at the time of writing.</p>
<p>To CSS authors, these queries are not recommended.</p>
<h2>Appendix: Methodology</h2>
<p>I made a diagnostics page to observe what information browsers expose to CSS and JavaScript APIs:</p>
<p>👉 <a href="https://danburzo.github.io/browser-summary/">Browser Summary</a> 👈</p>
<p>So far I have looked at the browsers I had at hand:</p>
<ul>
<li>Firefox macOS</li>
<li>Chrome macOS</li>
<li>Safari macOS 12 & 13 (Technical Preview)</li>
<li>Safari iOS 13</li>
<li>Safari iPadOS</li>
</ul>
<p>In addition, I've used Browserstack to check:</p>
<ul>
<li>Internet Explorer 11</li>
<li>Microsoft Edge (I couldn't find a way to change the default font settings from within Edge itself, so I'm not 100% sure about their impact)</li>
</ul>
<h3>Measuring Media Queries</h3>
<p>Where do the values for <code>min-width</code>, <code>min-height</code>, et cetera come from on the diagnostics page?</p>
<p>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 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"><code>Window.matchMedia()</code></a> method:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> query <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span><span class="token string">'(min-width: 10rem)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>query<span class="token punctuation">.</span>matches<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// ...</span><br /><span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token comment">// ...</span><br /><span class="token punctuation">}</span></code></pre>
<p>This allows us to check <code>min-width</code> 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. <code>min-width</code>) stops matching — that is, the reverse of what <code>matchMedia()</code> was designed for.</p>
<p>Technically, the <a href="https://drafts.csswg.org/cssom-view/#extensions-to-the-window-interface">CSSOM View Module spec</a> 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's mouth.</p>
<p>To do that, we can (ab)use <code>matchMedia</code> to learn the breakpoint of our current browser/device by asking repeatedly with different values. I used <a href="https://en.wikipedia.org/wiki/Bisection_method">the bisection method</a> to avoid making a gazillion queries:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">find_min_width</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> start <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 0 px</span><br /> <span class="token keyword">let</span> end <span class="token operator">=</span> <span class="token number">1000000</span><span class="token punctuation">;</span> <span class="token comment">// 1 million px</span><br /> <span class="token keyword">let</span> precision <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// whole pixels</span><br /><br /> <span class="token keyword">while</span> <span class="token punctuation">(</span>end <span class="token operator">-</span> start <span class="token operator">>=</span> precision<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> midpoint <span class="token operator">=</span> start <span class="token operator">+</span> <span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> query <span class="token operator">=</span> <span class="token function">matchMedia</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">(min-width: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>midpoint<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>query<span class="token punctuation">.</span>matches<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> start <span class="token operator">=</span> midpoint<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> end <span class="token operator">=</span> midpoint<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">round</span><span class="token punctuation">(</span>start<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This function returns the breakpoint value, in pixels, of the <code>min-width</code> media query for our current environment. The same technique, with some adjustments to the precision, can be used for <code>em</code>, <code>rem</code>, and <code>vw</code>.</p>
A CSS-only layout debugger2019-09-16T00:00:00Zhttps://danburzo.ro/css-layout-debugger/<p>I've recently come across Gajus Kuizinas' <a href="https://dev.to/gajus/my-favorite-css-hack-32g3">Favorite CSS hack</a>. I had used basic CSS debug styles (also known as <a href="https://meyerweb.com/eric/thoughts/2007/09/07/diagnostic-styling/">diagnostic CSS</a>) before, and kind of love their simplicity. I wondered if I could take them up a notch. The challenge?</p>
<p><strong>Create a CSS-only element inspector</strong>: as you hover elements, show their bounding box, and how their descendants are laid out — all with minimal disruption to the layout.</p>
<h2>The Approach</h2>
<p>You can see what I came up with <a href="https://danburzo.ro/demos/css-only-layout-debugger.html">on this demo page</a>, and follow along as I go into the thought process, the dead-ends, and interesting tidbits of CSS I learned in the process.</p>
<h3>Basic styling</h3>
<p>To show an element's box, we can <code>outline</code> it:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The <code>outline</code> property does not alter the layout since it's painted on top of elements, and authors usually only include outline styles for focused elements, so it's relatively innocuous.</p>
<p>To discern how the elements are nested, let's also add <a href="https://en.wikipedia.org/wiki/Onionskin">onionskin</a> backgrounds:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>So far so good! This is starting to look promising, but everything is quite red.</p>
<h3>Cycling the hue</h3>
<p>To further distinguish elements at different levels of nesting, let's vary the outline / background color (as in <a href="https://dev.to/gajus/my-favorite-css-hack-32g3">Gajus' example</a>). The <code>hsl()</code> notation, which is one of the few ways to obtain color variations in CSS right now, is a good candidate for expressing our colors.</p>
<p>First, a bit of refactoring, to bring <code>red</code> and <code>rgba(255, 0, 0, 0.1)</code> together:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token comment">/* red */</span><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>With the <code>--hue</code> 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.</p>
<blockquote>
<p><strong>Note:</strong> HSL is good, but not <em>great</em>. It replaces the machine-oriented red, green, and blue channels with something humans can better relate to — hue, saturation, and lightness. It'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, <a href="https://www.w3.org/TR/css-color-4/#lab-colors">the <code>lch()</code> color notation</a> will offer a better approximation.</p>
</blockquote>
<p>How do we go about cycling the hue in, let'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:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> + 60<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>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 <a href="https://github.com/w3c/csswg-drafts/issues/1594#issuecomment-382832667">get a way</a> to let an element redefine a custom property based on the value it has inherited from its ancestors, but until then, no dice.</p>
<p>Leafing through the <a href="https://www.w3.org/TR/css-values-4/">CSS values and units spec</a>, I was surprised to find <a href="https://www.w3.org/TR/css-values-4/#toggle-notation">the <code>toggle()</code> function</a>. It enables elements to cycle over a set of values instead of inheriting the value. You'd be able to write:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">toggle</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 60<span class="token punctuation">,</span> 120<span class="token punctuation">,</span> 180<span class="token punctuation">,</span> 240<span class="token punctuation">,</span> 300<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>...to get elements at each subsequent level to move 60 degrees away from their parent. Nice and clear and... unsupported. Even though the <code>toggle()</code> idea has been floating around <a href="http://lists.w3.org/Archives/Public/www-style/1999May/0067">since as early as 1999</a>, no browsers implement it at the time of writing.</p>
<p>So, unless I'm missing a clever workaround, we're back to old-school:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 120<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 180<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 240<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 300<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This setup caps out at the 300 hue (a nice fuchsia), but you can continue to cycle it for as many <code>* > *</code> selectors as your heart lets you.</p>
<h3>Showing the padding around elements</h3>
<p>If the browser barely broke a sweat from anything we did so far, it's time to raise the temperature with our next mini-challenge: showing padding visually.</p>
<p>We're going to need something that lets us hook into the element's content box, and its padding box, independently. Hello <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-origin"><code>background-origin</code></a>!</p>
<p>The <code>background-origin</code> property is only meant for background <em>images</em>, not background colors, so we need a way to make a background image out of a solid color. The <a href="https://drafts.csswg.org/css-images-4/">CSS Images Level 4</a> spec defines <a href="https://drafts.csswg.org/css-images-4/#image-notation">the <code>image()</code> syntax</a> which accepts a color, such as <code>image(fuchsia)</code>, to produce an image. But until browsers support it, we need to improvise with gradients.</p>
<p>The shortest formula to get a solid color with the gradient syntax is, as far as I know:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>fuchsia<span class="token punctuation">,</span> fuchsia<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>To get different colors for the content box and the padding box, we layer two of these images and define their <code>background-origin</code> separately:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>fuchsia<span class="token punctuation">,</span> fuchsia<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>yellow<span class="token punctuation">,</span> yellow<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">background-origin</span><span class="token punctuation">:</span> content-box<span class="token punctuation">,</span> padding-box<span class="token punctuation">;</span><br /> <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>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's important to include <code>background-repeat: no-repeat</code> for it to work as expected, which I also tend to forget.</p>
<blockquote>
<p><strong>Note:</strong> You can probably get a similar effect with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip"><code>background-clip</code></a> instead of the <code>background-origin</code> / <code>background-repeat</code> combo, but I haven't looked into it.</p>
</blockquote>
<p>For a cooler look, like the one you sometimes see in dev tools, let's turn our yellow padding box into nice diagonal stripes. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/repeating-linear-gradient"><code>repeating-linear-gradient</code></a> is useful for this:</p>
<pre class="language-css"><code class="language-css"> <span class="token punctuation">{</span><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span><br /> 45deg<span class="token punctuation">,</span><br /> <span class="token comment">/* angle for diagonals */</span> fuchsia<span class="token punctuation">,</span><br /> fuchsia 1px<span class="token punctuation">,</span><br /> <span class="token comment">/* our 1px stripes */</span> transparent 1px<span class="token punctuation">,</span><br /> transparent 3px <span class="token comment">/* 2px space between stripes */</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If we work it into our inspector code:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Opaque version of the color */</span><br /> <span class="token property">--c-solid</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/* Translucent version of the color */</span><br /> <span class="token property">--c-bg</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <br /> <br /> <span class="token comment">/* Content box fill */</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token comment">/* Content box white underpaint */</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>white<span class="token punctuation">,</span> white<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">/* Padding box stripes */</span><br /> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>45deg<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span> 1px<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span><br /> --c-bg<br /> <span class="token punctuation">)</span> 1px<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span> 3px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">background-origin</span><span class="token punctuation">:</span> content-box<span class="token punctuation">,</span> content-box<span class="token punctuation">,</span> padding-box<span class="token punctuation">;</span><br /><br /> <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>To make the combo work with stripes, you may notice we've added a coat of white paint over the stripes along the context box, to obscure them.</p>
<h3>Making our inspector hover-aware</h3>
<p>So far we'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're hovering.</p>
<p>Small problem: when you hover an element on the page, you don'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.)</p>
<p>The closest we can get to the ideal is to change <code>*</code> (every element on the page) to <code>body :hover, body :hover > *</code>, which means <em>hovered elements and their direct descendants</em>. We're also not styling <code>body</code> itself, since it doesn't provide too much information and its add visual noise.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">body :hover,<br />body :hover > *</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Opaque version */</span><br /> <span class="token property">--c-solid</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/* Translucent version */</span><br /> <span class="token property">--c-bg</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <br /> <br /> <span class="token comment">/* Content box fill */</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token comment">/* Content box white underpaint */</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>white<span class="token punctuation">,</span> white<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">/* Padding box stripes */</span><br /> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>45deg<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span> 1px<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span><br /> --c-bg<br /> <span class="token punctuation">)</span> 1px<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span> 3px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">background-origin</span><span class="token punctuation">:</span> content-box<span class="token punctuation">,</span> content-box<span class="token punctuation">,</span> padding-box<span class="token punctuation">;</span><br /> <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><strong>A note on <code>:focus</code>.</strong> Unlike <code>:hover</code>, only one element at a time has <code>:focus</code>, 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:</p>
<pre class="language-js"><code class="language-js">Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">el</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> el<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'tabindex'</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> e<span class="token punctuation">.</span><span class="token function">stopPropagation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>...which takes away from the beauty of a CSS-only solution.</p>
<h3>Extra credits: identifying the elements</h3>
<p>One last mini-challenge: could we show information about elements — their tag name, ID and classes – as we hover them?</p>
<p>CSS has <a href="https://www.w3.org/TR/css-values-4/#attr-notation">the <code>attr()</code> function</a> to read attributes from HTML elements and use them in styles. At the time of writing, we can only use <code>attr()</code> as the <code>content</code> of <code>::before</code>and <code>::after</code> pseudo-elements, but for our modest goals it will do nicely. We can extract an element's <code>class</code> and <code>id</code> — but not its tag name — and display them as a floating label:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">[id]:hover::before,<br />[class]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span><br /> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> -100%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 80%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">:not([id])[class]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'.'</span> <span class="token function">attr</span><span class="token punctuation">(</span>class<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">:not([class])[id]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'#'</span> <span class="token function">attr</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">[id][class]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'#'</span> <span class="token function">attr</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token string">'.'</span> <span class="token function">attr</span><span class="token punctuation">(</span>class<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Let's unpack that.</p>
<p>The <code>[id]:hover::before, [class]:hover::before</code> selector matches the <code>::before</code> pseudo-element of hovered elements which have either an ID or a class attribute attached to them.</p>
<p>Because we can't set these elements' <code>content</code> conditionally (based on which of the ID / class attributes are present) we set it for elements which have both (<code>[id][class]</code>), and separately for ones which have just one (<code>:not([id])[class]</code>) or the other (<code>:not([class])[id]</code>).</p>
<p><code>position: absolute</code> takes the <code>::before</code> pseudo-element out of the normal flow. By not specifying <code>top</code> and <code>left</code> offsets we leave it in its default place at the top-left hand corner of its parent's content box. Instead, we use <code>transform(0, -100%)</code> to pull it upwards, so it sits on top of its parent.</p>
<p><strong>Note:</strong> Since we're taking over the <code>::before</code> 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 <code>position: relative</code> on the parent and explicit <code>top</code> and <code>left</code> offsets, with the risk of further altering the original layout.</p>
<h3>All together now</h3>
<p>Here is the final version of the code:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* <br /> Hue rotation <br /> ------------<br />*/</span><br /><br /><span class="token selector">*</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 120<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 180<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 240<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 300<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 120<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 180<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > * > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 240<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token selector">* > * > * > * > * > * > * > * > * > * > * > *</span> <span class="token punctuation">{</span><br /> <span class="token property">--hue</span><span class="token punctuation">:</span> 300<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* <br /> Draw elements' boxes<br /> --------------------<br />*/</span><br /><br /><span class="token selector">body :hover,<br />body :hover > *</span> <span class="token punctuation">{</span><br /> <span class="token comment">/* Opaque version */</span><br /> <span class="token property">--c-solid</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">/* Translucent version */</span><br /> <span class="token property">--c-bg</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 50%<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">background-image</span><span class="token punctuation">:</span> <br /> <br /> <span class="token comment">/* Content box fill */</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token comment">/* Content box white underpaint */</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>white<span class="token punctuation">,</span> white<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">/* Padding box stripes */</span><br /> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>45deg<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-solid<span class="token punctuation">)</span> 1px<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span><br /> --c-bg<br /> <span class="token punctuation">)</span> 1px<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--c-bg<span class="token punctuation">)</span> 3px<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token property">background-origin</span><span class="token punctuation">:</span> content-box<span class="token punctuation">,</span> content-box<span class="token punctuation">,</span> padding-box<span class="token punctuation">;</span><br /> <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/* <br /> Show elements' classes / ID<br /> ---------------------------<br />*/</span><br /><br /><span class="token selector">[id]:hover::before,<br />[class]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span><br /> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> -100%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 80%<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">:not([id])[class]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'.'</span> <span class="token function">attr</span><span class="token punctuation">(</span>class<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">:not([class])[id]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'#'</span> <span class="token function">attr</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">[id][class]:hover::before</span> <span class="token punctuation">{</span><br /> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'#'</span> <span class="token function">attr</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token string">'.'</span> <span class="token function">attr</span><span class="token punctuation">(</span>class<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<blockquote>
<p>To make it more resilient, we can sprinkle some <code>!important</code> keywords, but that's left as an exercise to the reader.</p>
</blockquote>
<p>Here's <a href="https://danburzo.ro/demos/css-only-layout-debugger.html">the demo page again</a>. That is all! ✌️</p>
My favorite records from 20182018-12-12T00:00:00Zhttps://danburzo.ro/favorite-records-2018/<ul>
<li>Abul Mogard - Above All Dreams</li>
<li>Against All Logic - 2012 - 2017</li>
<li>Alva Noto - Unieqav</li>
<li>Amen Dunes - Freedom</li>
<li>Amnesia Scanner - Another Life</li>
<li>Aphex Twin - Collapse EP</li>
<li>Autechre - NTS Sessions 1</li>
<li>AWVFTS - Long it May Sustain</li>
<li>Balmorrhea - Clear Language (Reworked)</li>
<li>Beak> - >>>; LA Playback</li>
<li>Biosphere - The Hilvarenbeek Recordings</li>
<li>Blawan - Wet Will Always Dry</li>
<li>Blanck Mass - Odd Scene / Shit Luck</li>
<li>Brian Eno & Kevin Shields - The Weight of History / Only Once Away my Son</li>
<li>Bruce Brubaker - Codex</li>
<li>Bruno Sanfilippo - Unity</li>
<li>Bucharest - Budapest</li>
<li>Chilly Gonzalez - Piano Solo III</li>
<li>Daniel Avery - Song for Alpha</li>
<li>Daniel Bjarnasson - Collider; Under the Tree</li>
<li>David August - D'Angelo; DCXXXIX A.C.</li>
<li>David Bryne - American Utopia</li>
<li>Deafhaven - Ordinary Corrupt Human Love</li>
<li>Dead Can Dance - Dionysus</li>
<li>Demdike Stare - Passion</li>
<li>Dita von Teese - Remix</li>
<li>☞ Dirtmusic - Bu Bir Ruya</li>
<li>Donato Dozzy - Filo Loves the Acid</li>
<li>Driftmachine - Shunter</li>
<li>Dustin O'Halloran - The Hate U Give OST; Puzzle OST</li>
<li>EELS - The Deconstruction</li>
<li>Eric Whitacre - Deep Field</li>
<li>Federico Albanese - By the Deep Sea</li>
<li>Fever Ray - Mustn't Hurry (remixes)</li>
<li>Film School - Bright to Death</li>
<li>Gas - Rausch</li>
<li>Gazelle Twin - Pastoral</li>
<li>Grouper - Field of Points</li>
<li>GusGus - Lies are More Flexible</li>
<li>Haroumi Hosono - NAGA</li>
<li>Ian William Craig - Thresholder</li>
<li>Ilya Beshevli - Deja Vu</li>
<li>Interpol - Marauder</li>
<li>Jacques Greene - Fever Focus</li>
<li>Joep Beving - Conatus</li>
<li>Jóhann Jóhannsson - Mandy OST; The Mercy OST</li>
<li>Jon Hopkins - Singularity</li>
<li>Josh T. Pearson - The Straight Hits!</li>
<li>Julia Holter - Aviary</li>
<li>Kenneth James Gibson - In the Fields of Nothing</li>
<li>Khruangbin - Como Todo el Mundo</li>
<li>Laurel Halo - Raw Silk Uncut Wood</li>
<li>Lubomyr Melnyk - Fallen Trees</li>
<li>Marianne Faithfull - Negative Capability</li>
<li>Marissa Nadler - For my Crimes</li>
<li>Mark Barrott - Nature Sounds of the Balearics</li>
<li>Max Würden - Momentum</li>
<li>Mogwai - Kin OST</li>
<li>Niklas Paschburg - Oceanic</li>
<li>Nils Frahm - All Melody; Encores 1</li>
<li>Nine Inch Nails - Bad Witch</li>
<li>Okkervil River - In the Rainbow Rain</li>
<li>Oneohtrix Point Never - Age Of</li>
<li>Oscar Mulero - Electric Shades EP</li>
<li>OZmotic - Elusive Balance</li>
<li>Peter Bjorn and John - Darker Days</li>
<li>Phosphorescent - C'Est la Vie</li>
<li>Rhye - Blood; Blood Remixed</li>
<li>Roger Eno - Dust of Stars</li>
<li>Roman Flügel - Themes</li>
<li>Ryuichi Sakamoto - Async Remodels</li>
<li>Sevdaliza - The Calling</li>
<li>Sigur Rós - Route One</li>
<li>Slow Meadow - Screensaver Prelude</li>
<li>Soundwalk Collective - Death Must Die</li>
<li>Spiritualized - And Nothing Hurt</li>
<li>Stuart A. Staples - Arrythmia</li>
<li>Taylor Deupree - Fallen</li>
<li>The Black Dog - Black Daisy Wheel; Post-Truth</li>
<li>The Blaze - Dancehall</li>
<li>The Brian Jonestown Massacre - Something Else</li>
<li>The Field - Infinite Moment</li>
<li>The Growlers - Casual Acquaintances</li>
<li>The Limiñanas - I've got trouble in mind; Shadow People</li>
<li>This Will Destroy You - New Others Part One; Part Two</li>
<li>Thom Yorke - Suspiria</li>
<li>Tigran Hamasyan - For Gyumri EP</li>
<li>Tim Hecker - Konoyo</li>
<li>uZiq - Challenge Me Foolish</li>
<li>V.A. - Climax OST</li>
<li>V.A. - Liminal</li>
<li>V.A. - In Death's Dream Kingdom</li>
<li>Vanessa Wagner - Liszt, Pärt</li>
<li>Vessel - Queen of Golden Dogs</li>
<li>WhoMadeWho - Through the Walls</li>
<li>Young Fathers - Cocoa Sugar</li>
</ul>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
My favorite records from 20172017-12-20T00:00:00Zhttps://danburzo.ro/favorite-records-2017/<p>Here's this year's records, listed alphabetically, with favorites in bold.</p>
<table>
<thead>
<tr>
<th>Album</th>
<th>Bandcamp / SoundCloud / YT</th>
<th>Apple Music</th>
</tr>
</thead>
<tbody>
<tr>
<td>Aldous Harding — Party</td>
<td><a href="https://soundcloud.com/aldous-harding/sets/party-1444">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/party/1214279887">🍎</a></td>
</tr>
<tr>
<td>Alessandro Cortini — AVANTI</td>
<td><a href="https://soundcloud.com/alessandrocortiniofficial/sets/avanti-6">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/avanti/1267405189">🍎</a></td>
</tr>
<tr>
<td>Alex Cameron — Forced Witness</td>
<td><a href="https://alkcm.bandcamp.com/album/forced-witness">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/forced-witness/1250563294">🍎</a></td>
</tr>
<tr>
<td>ANOHNI — Paradise EP</td>
<td><a href="https://anohni.bandcamp.com/album/paradise">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/paradise-ep/1194887350">🍎</a></td>
</tr>
<tr>
<td>Anton Kubikov — Whatness</td>
<td><a href="https://kubikov.bandcamp.com/album/whatness">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/whatness/1234950603">🍎</a></td>
</tr>
<tr>
<td>Astrïd & Rachel Grimes — Through the Sparkle</td>
<td><a href="https://gizehrecords.bandcamp.com/album/through-the-sparkle">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/through-the-sparkle/1257702684">🍎</a></td>
</tr>
<tr>
<td>Balmorhea — Clear Language</td>
<td><a href="https://balmorhea.bandcamp.com/album/clear-language">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/clear-language/1252681306">🍎</a></td>
</tr>
<tr>
<td>Bedouine — Bedouine</td>
<td><a href="https://itunes.apple.com/ro/album/bedouine-deluxe/1312847102">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/bedouine-deluxe/1312847102">🍎</a></td>
</tr>
<tr>
<td>Ben Frost — The Centre Cannot Hold</td>
<td><a href="https://benfrost.bandcamp.com/album/the-centre-cannot-hold">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/the-centre-cannot-hold/1267617451">🍎</a></td>
</tr>
<tr>
<td>Björk — Utopia</td>
<td>—</td>
<td><a href="https://itunes.apple.com/ro/album/utopia/1303711887">🍎</a></td>
</tr>
<tr>
<td><strong>Blanck Mass — World Eater</strong></td>
<td><a href="https://blanckmass.bandcamp.com/album/world-eater">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/world-eater/1191862517">🍎</a></td>
</tr>
<tr>
<td>Blonde Redhead — 3 O'Clock EP</td>
<td>—</td>
<td><a href="https://itunes.apple.com/ro/album/3-oclock-ep/1204160203">🍎</a></td>
</tr>
<tr>
<td>Blondie — Pollinator</td>
<td>—</td>
<td><a href="https://itunes.apple.com/ro/album/pollinator/1198810651">🍎</a></td>
</tr>
<tr>
<td>Brainwaltzera — Poly-Ana</td>
<td><a href="https://futureislisteningmusic.bandcamp.com/album/brainwaltzera-poly-ana-2">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/poly-ana/1267331848">🍎</a></td>
</tr>
<tr>
<td>Brian Eno — Reflection</td>
<td>—</td>
<td><a href="https://itunes.apple.com/ro/album/reflection/1176137149">🍎</a></td>
</tr>
<tr>
<td>Burial — Rodent</td>
<td><a href="https://burial.bandcamp.com/album/rodent-hdb113">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/rodent-single/1280387338">🍎</a></td>
</tr>
<tr>
<td>Cameron Avery — Ripe Dreams, Pipe Dreams</td>
<td><a href="https://cameronavery.bandcamp.com/album/ripe-dreams-pipe-dreams">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/ripe-dreams-pipe-dreams-deluxe-edition/1188024147">🍎</a></td>
</tr>
<tr>
<td>Chilly Gonzales & Jarvis Cocker — Room 29</td>
<td>—</td>
<td><a href="https://itunes.apple.com/ro/album/room-29/1194280442">🍎</a></td>
</tr>
<tr>
<td><strong>Charlotte Gainsbourg — Rest</strong></td>
<td><a href="https://www.youtube.com/watch?v=eRwgL_PrQYQ&list=PL-d2FrHULiHKb4I4YfjgJVMx1TEkLMfNW">YouTube</a></td>
<td><a href="https://itunes.apple.com/ro/album/rest/1269631968">🍎</a></td>
</tr>
<tr>
<td>Cigarettes After Sex — Cigarettes After Sex</td>
<td><a href="https://cigarettesaftersex.bandcamp.com/album/cigarettes-after-sex">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/cigarettes-after-sex/1215408950">🍎</a></td>
</tr>
<tr>
<td>Courtney Barnett & Kurt Vile — Lotta Sea Lice</td>
<td><a href="https://courtneybarnettandkurtvile.bandcamp.com/album/lotta-sea-lice">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/lotta-sea-lice/1270169831">🍎</a></td>
</tr>
<tr>
<td>Daniele Luppi & Parquet Courts — Milano</td>
<td><a href="https://soundcloud.com/danieleluppi/sets/milano-5">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/milano/1279328251">🍎</a></td>
</tr>
<tr>
<td>Delia Gonzalez — Horse Follows Darkness</td>
<td><a href="https://deliagonzalez.bandcamp.com/album/horse-follows-darkness">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/horse-follows-darkness/1221430830">🍎</a></td>
</tr>
<tr>
<td>Dustin O'Halloran — 3 Movements</td>
<td><a href="https://1631recordings.bandcamp.com/album/3-movements">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/3-movements-single/1210078388">🍎</a></td>
</tr>
<tr>
<td>Eluvium — Shuffle Drones</td>
<td><a href="https://eluvium.bandcamp.com/album/shuffle-drones">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/shuffle-drones/1298237250">🍎</a></td>
</tr>
<tr>
<td>Fatima Al Qadiri — Shaneera</td>
<td><a href="https://fatimaalqadiri.bandcamp.com/album/hdb110-shaneera">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/shaneera-ep/1282016852">🍎</a></td>
</tr>
<tr>
<td>Fever Ray — Plunge</td>
<td><a href="https://soundcloud.com/fever-ray/sets/plunge-4">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/plunge/1298376912">🍎</a></td>
</tr>
<tr>
<td><strong>Four Tet — New Energy</strong></td>
<td><a href="https://fourtet.bandcamp.com/album/new-energy">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/new-energy/1288517633">🍎</a></td>
</tr>
<tr>
<td><strong>Gas — Narkopop</strong></td>
<td><a href="https://kompakt-gas.bandcamp.com/album/narkopop">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/narkopop/1212939308">🍎</a></td>
</tr>
<tr>
<td>Godspeed You! Black Emperor — Luciferian Towers</td>
<td><a href="https://godspeedyoublackemperor.bandcamp.com/album/luciferian-towers">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/luciferian-towers/1264190010">🍎</a></td>
</tr>
<tr>
<td>Grizzly Bear — Painted Ruins</td>
<td>—</td>
<td><a href="https://itunes.apple.com/ro/album/painted-ruins/1235159880">🍎</a></td>
</tr>
<tr>
<td>High Plains — Cinderland</td>
<td><a href="https://highplainskranky.bandcamp.com/album/cinderland">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/cinderland/1186064831">🍎</a></td>
</tr>
<tr>
<td>Iron & Wine — Beast Epic</td>
<td><a href="https://ironandwine.bandcamp.com/album/beast-epic">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/beast-epic/1238344396">🍎</a></td>
</tr>
<tr>
<td>Japandroids — Near to the Wild Heart of Life</td>
<td><a href="https://japandroids.bandcamp.com/album/near-to-the-wild-heart-of-life">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/near-to-the-wild-heart-of-life/1168964814">🍎</a></td>
</tr>
<tr>
<td>Jefre Cantu-Ledesma — On the Echoing Green</td>
<td><a href="https://jefrecantu-ledesma.bandcamp.com/album/on-the-echoing-green">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/on-the-echoing-green/1213843979">🍎</a></td>
</tr>
<tr>
<td>Jlin — Black Origami</td>
<td><a href="https://jlin.bandcamp.com/album/black-origami">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/black-origami/1213597238">🍎</a></td>
</tr>
<tr>
<td>Joni Void — Selfless</td>
<td><a href="https://jonivoid.bandcamp.com/album/selfless">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/selfless/1208268417">🍎</a></td>
</tr>
<tr>
<td>Justin Walter — Unseen Forces</td>
<td><a href="https://justinwalter.bandcamp.com/album/unseen-forces-2">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/unseen-forces/1207130602">🍎</a></td>
</tr>
<tr>
<td>Kastil — The Sadistic Abbess</td>
<td><a href="https://soulnotes.bandcamp.com/album/the-sadistic-abbess-st171">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/the-sadistic-abbess/1200874669">🍎</a></td>
</tr>
<tr>
<td>Kelly Lee Owens — Kelly Lee Owens</td>
<td><a href="https://kellyleeowens.bandcamp.com/album/kelly-lee-owens">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/kelly-lee-owens/1195541100">🍎</a></td>
</tr>
<tr>
<td>Kendrick Lamar — DAMN.</td>
<td><a href="https://soundcloud.com/kendrick-lamar-music/sets/damn-86">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/damn/1223618217">🍎</a></td>
</tr>
<tr>
<td>King Krule — The OOZ</td>
<td><a href="https://soundcloud.com/kingkruleofficial/sets/the-ooz-1">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/the-ooz/1273304020">🍎</a></td>
</tr>
<tr>
<td><strong>Kristoffer Lo — Anhedonia</strong></td>
<td><a href="https://soundcloud.com/kristofferloofficial/sets/anhedonia-6">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/anhedonia/1211343879">🍎</a></td>
</tr>
<tr>
<td>Lana Del Rey — Lust for Life</td>
<td><a href="https://soundcloud.com/lana-del-rey/sets/lust-for-life-6">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/lust-for-life/1255937240">🍎</a></td>
</tr>
<tr>
<td>Laraaji — Sun Gong</td>
<td><a href="https://soundcloud.com/all-saints-records/15_sun-gong-2-gong-sun-edit-by">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/sun-gong/1260592601">🍎</a></td>
</tr>
<tr>
<td>LCD Soundsystem - american dream</td>
<td><a href="https://soundcloud.com/lcd-soundsystem/sets/american-dream-11">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/american-dream/1258822744">🍎</a></td>
</tr>
<tr>
<td>Leandro Fresco & Rafael Anton Irisarri — La Equidistancia</td>
<td><a href="https://astrangelyisolatedplace.bandcamp.com/album/la-equidistancia">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/la-equidistancia/1226211433">🍎</a></td>
</tr>
<tr>
<td>Lift to Experience — The Texas-Jerusalem Crossroads <em>(Reissue)</em></td>
<td><a href="https://soundcloud.com/lifttoexperience/sets/the-texas-jerusalem-crossroads">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/the-texas-jerusalem-crossroads/1172400599">🍎</a></td>
</tr>
<tr>
<td>Mdou Moctar — Sousoume Tamachek</td>
<td><a href="https://mdoumoctar.bandcamp.com/album/sousoume-tamachek">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/sousoume-tamachek/1282451487">🍎</a></td>
</tr>
<tr>
<td>Mogwai — Every Country's Sun</td>
<td><a href="https://temporaryresidence.bandcamp.com/album/every-countrys-sun">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/every-countrys-sun/1232654304">🍎</a></td>
</tr>
<tr>
<td><strong>Murcof & Vanessa Wagner — EP02 & EP03</strong></td>
<td><a href="https://infine-rec.bandcamp.com/album/ep-02">EP02</a>, <a href="https://infine-rec.bandcamp.com/album/ep03">EP03</a></td>
<td><a href="https://itunes.apple.com/ro/album/ep02-single/1235207906">EP02</a>, <a href="https://itunes.apple.com/ro/album/ep03-ep/1299863527">EP03</a></td>
</tr>
<tr>
<td>Oscar Mulero — Pattern Series Compilation</td>
<td><a href="https://soundcloud.com/oscarmulero/sets/oscar-mulero-pattern-series">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/pattern-series-compilation/1302916574">🍎</a></td>
</tr>
<tr>
<td>Oxbow — The Black Duke</td>
<td><a href="https://oxbowofficial.bandcamp.com/album/thin-black-duke">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/thin-black-duke/1222102515">🍎</a></td>
</tr>
<tr>
<td><strong>Ryuichi Sakamoto - async</strong></td>
<td><a href="https://soundcloud.com/ryuichi-sakamoto-official/sets/async-2">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/async/1218980002">🍎</a></td>
</tr>
<tr>
<td><strong>Sevdaliza — Ison</strong></td>
<td><a href="https://www.youtube.com/watch?v=znV9KDsNtXY">YouTube</a></td>
<td><a href="https://itunes.apple.com/ro/album/ison/1314724379">🍎</a></td>
</tr>
<tr>
<td>St. Vincent — MASSEDUCTION</td>
<td><a href="https://soundcloud.com/st_vincent/sets/masseduction">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/masseduction/1276543346">🍎</a></td>
</tr>
<tr>
<td>Tale of Us — Endless</td>
<td><a href="https://soundcloud.com/taleofus/sets/endless-18">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/endless/1206594945">🍎</a></td>
</tr>
<tr>
<td>The Acid — The Bomb OST</td>
<td><a href="https://soundcloud.com/theacid/sets/the-bomb-original-motion">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/the-bomb-original-motion-picture-soundtrack/1293679751">🍎</a></td>
</tr>
<tr>
<td><strong>The Blaze — Territory EP</strong></td>
<td><a href="https://www.youtube.com/playlist?list=PLMalG3HUd0QQd0FT1ll32P9gp9k4cOlT0">YouTube</a></td>
<td><a href="https://itunes.apple.com/ro/album/territory-ep/1203811796">🍎</a></td>
</tr>
<tr>
<td><strong>The National — Sleep Well Beast</strong></td>
<td><a href="https://www.youtube.com/watch?v=GwZvip416NU&list=PLIrpDAtP87O5jypS5PGgHWCH6qCwR-efr">YouTube</a></td>
<td><a href="https://itunes.apple.com/ro/album/sleep-well-beast/1233837225">🍎</a></td>
</tr>
<tr>
<td>The War on Drugs — A Deeper Understanding</td>
<td><a href="https://soundcloud.com/thewarondrugs/sets/a-deeper-understanding-1">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/a-deeper-understanding/1242366660">🍎</a></td>
</tr>
<tr>
<td>Timber Timbre — Sincerely, Future Pollution</td>
<td><a href="https://timbertimbre.bandcamp.com/album/sincerely-future-pollution">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/sincerely-future-pollution/1187851168">🍎</a></td>
</tr>
<tr>
<td>Tom Rogerson & Brian Eno — Finding Shore</td>
<td><a href="https://tomrogerson.bandcamp.com/album/finding-shore">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/finding-shore/1287106205">🍎</a></td>
</tr>
<tr>
<td>Visible Cloaks — Reassemblage</td>
<td><a href="https://visiblecloaks.bandcamp.com/album/reassemblage">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/reassemblage/1176716928">🍎</a></td>
</tr>
<tr>
<td>Waxahatchee — Out in the Storm</td>
<td><a href="https://waxahatchee.bandcamp.com/album/out-in-the-storm">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/out-in-the-storm-deluxe-version/1222703546">🍎</a></td>
</tr>
<tr>
<td>William Basinski — A Shadow in Time</td>
<td><a href="https://williambasinski.bandcamp.com/album/a-shadow-in-time">Bandcamp</a></td>
<td><a href="https://itunes.apple.com/ro/album/a-shadow-in-time/1185573890">🍎</a></td>
</tr>
<tr>
<td>yaeji — EP2</td>
<td><a href="https://soundcloud.com/godmodemusic/sets/yaeji-ep2-godmode">SoundCloud</a></td>
<td><a href="https://itunes.apple.com/ro/album/ep2/1297894762">🍎</a></td>
</tr>
</tbody>
</table>
<hr />
<p>A significant album I was introduced to this year is the 2012 <em>Changeling</em> by Camille O'Sullivan <a href="https://itunes.apple.com/ro/album/changeling/1187095541">🍎</a>, which has been on heavy rotation throughout. It starts with a cover of Gillian Welch's <strong>Revelator</strong>, about which I got excited <a href="https://danburzo.ro/favorite-records-2016#addendum-dec-16-2016">this time last year</a>.</p>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>Other lists:</p>
<ul>
<li><a href="https://www.roughtrade.com/gb/albums-of-the-year">Rough Trade</a></li>
<li><a href="http://thequietus.com/articles/23660-albums-of-the-year-2017">The Quietus</a></li>
<li><a href="https://bleep.com/Albums-of-the-Year-2017">Bleep</a></li>
<li><a href="https://boomkat.com/charts/2017/558">Boomkat</a></li>
</ul>
My favorite records from 20162016-12-06T00:00:00Zhttps://danburzo.ro/favorite-records-2016/<p>It'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've listened to more closely and have enjoyed. My ten favorites are in bold.</p>
<table>
<thead>
<tr>
<th>Album</th>
<th>♫</th>
<th>🍏</th>
</tr>
</thead>
<tbody>
<tr>
<td>Adam Bryanbaum Wiltzie – Salero</td>
<td><a href="https://erasedtapes.bandcamp.com/album/salero-original-motion-picture-soundtrack">♫</a></td>
<td><a href="https://itun.es/ro/CdbFeb">🍏</a></td>
</tr>
<tr>
<td>Andy Stott – Too Many Voices</td>
<td><a href="https://www.youtube.com/playlist?list=PLpOEGKO1FO736ksFZ6DpRyMSMVUzy-jB-">♫</a></td>
<td><a href="https://itun.es/ro/5gkXbb">🍏</a></td>
</tr>
<tr>
<td>ANOHNI – Hopelessness</td>
<td><a href="https://anohni.bandcamp.com/album/hopelessness">♫</a></td>
<td><a href="https://itun.es/ro/y8t3ab">🍏</a></td>
</tr>
<tr>
<td>A Winged Victory for the Sullen – Iris</td>
<td><a href="https://erasedtapes.bandcamp.com/album/iris-musique-originale-bonus-track-version">♫</a></td>
<td><a href="https://itun.es/ro/ip25fb">🍏</a></td>
</tr>
<tr>
<td><strong>Biosphere – Departed Glories</strong></td>
<td><a href="https://biosphere.bandcamp.com/album/departed-glories">♫</a></td>
<td><a href="https://itun.es/ro/OyBUdb">🍏</a></td>
</tr>
<tr>
<td>Brian Eno – The Ship</td>
<td><a href="https://www.youtube.com/watch?v=pn1riJSHhkY">♫</a></td>
<td><a href="https://itun.es/ro/ZuVMab">🍏</a></td>
</tr>
<tr>
<td>The Caretaker – Everywhere at the end of time</td>
<td><a href="https://thecaretaker.bandcamp.com/album/everywhere-at-the-end-of-time">♫</a></td>
<td>N/A</td>
</tr>
<tr>
<td>Car Seat Headrest – Teens of Denial</td>
<td><a href="https://carseatheadrest.bandcamp.com/album/teens-of-denial">♫</a></td>
<td><a href="https://itun.es/ro/t-xLcb">🍏</a></td>
</tr>
<tr>
<td>Circe – Circe</td>
<td><a href="https://soundcloud.com/sigur-ros/sets/circe">♫</a></td>
<td><a href="https://itun.es/ro/SVaV8">🍏</a></td>
</tr>
<tr>
<td>Colin Stetson – Sorrow: A reimagining of Gorecki's 3rd Symphony</td>
<td><a href="https://www.youtube.com/watch?v=8c7YvSMyIso">♫</a></td>
<td><a href="https://itun.es/ro/Pb6tab">🍏</a></td>
</tr>
<tr>
<td>Daniel Lanois – Goodbye to Language</td>
<td><a href="https://daniellanois.bandcamp.com/album/goodbye-to-language">♫</a></td>
<td><a href="https://itun.es/ro/3B5sdb">🍏</a></td>
</tr>
<tr>
<td>David Bowie – ★</td>
<td><a href="https://www.youtube.com/watch?v=kszLwBaC4Sw">♫</a></td>
<td><a href="https://itun.es/ro/JB7h_">🍏</a></td>
</tr>
<tr>
<td><strong>Demdike Stare – Wonderland</strong></td>
<td><a href="https://www.youtube.com/playlist?list=PLpOEGKO1FO72NbJ_neMLDDIM-4hkYhtWH">♫</a></td>
<td>N/A</td>
</tr>
<tr>
<td>Eluvium – False Readings On</td>
<td><a href="https://eluvium.bandcamp.com/album/false-readings-on">♫</a></td>
<td><a href="https://itun.es/ro/jqWmdb">🍏</a></td>
</tr>
<tr>
<td>Explosions in the Sky – The Wilderness</td>
<td><a href="https://explosionsinthesky.bandcamp.com/album/the-wilderness">♫</a></td>
<td><a href="https://itun.es/ro/pA64_">🍏</a></td>
</tr>
<tr>
<td>Floating Points – Kuiper</td>
<td><a href="https://soundcloud.com/floatingpoints/kuiper">♫</a></td>
<td><a href="https://itun.es/ro/F9vtcb">🍏</a></td>
</tr>
<tr>
<td>Gold Panda – Good Luck and Do Your Best</td>
<td><a href="https://itun.es/ro/F9vtcb">♫</a></td>
<td><a href="https://itun.es/ro/Pa8Qab">🍏</a></td>
</tr>
<tr>
<td>Kassel Jaeger, Stephan Mathieu, Akira Rabelais – Zauberberg</td>
<td><a href="https://schwebung.bandcamp.com/album/zauberberg">♫</a></td>
<td><a href="https://itun.es/ro/2PSvab">🍏</a></td>
</tr>
<tr>
<td>Jambinai – A Hermitage</td>
<td><a href="https://www.youtube.com/watch?v=FPRle456t88">♫</a></td>
<td><a href="https://itun.es/ro/AVkQab">🍏</a></td>
</tr>
<tr>
<td>Jherek Bischoff – Cistern</td>
<td><a href="https://jherekbischoff.bandcamp.com/album/cistern">♫</a></td>
<td><a href="https://itun.es/ro/TKDqcb">🍏</a></td>
</tr>
<tr>
<td><strong>Jóhann Jóhannsson – Orphée</strong></td>
<td><a href="https://www.youtube.com/watch?v=AlftMNmDH00">♫</a></td>
<td><a href="https://itun.es/ro/HD0udb">🍏</a></td>
</tr>
<tr>
<td>Jóhann Jóhannsson – Arrival</td>
<td><a href="https://www.youtube.com/watch?v=qsaRJ4j4xIo">♫</a></td>
<td><a href="https://itun.es/ro/OnUAfb">🍏</a></td>
</tr>
<tr>
<td>King Ghazi Presents Abu Sayah – Houran & Shamaleh</td>
<td><a href="https://soundcloud.com/versatile-records/sets/king-ghazi-presents-abu-sayah-houran-shamaleh">♫</a></td>
<td><a href="https://itun.es/ro/Y3Wvcb">🍏</a></td>
</tr>
<tr>
<td><strong>Kjartan Sveinsson – Der Klang der Offenbarung des Göttlichen</strong></td>
<td><a href="https://www.youtube.com/watch?v=MFiwD4LsbGw">♫</a></td>
<td><a href="https://itun.es/ro/eKYpfb">🍏</a></td>
</tr>
<tr>
<td>Klara Lewis – Too</td>
<td><a href="https://editionsmego.bandcamp.com/album/too">♫</a></td>
<td><a href="https://itun.es/ro/bTsmbb">🍏</a></td>
</tr>
<tr>
<td><strong>Kristoffer Lo – The Black Meat</strong></td>
<td><a href="https://www.youtube.com/watch?v=BhvkxdhDtS8">♫</a></td>
<td><a href="https://itun.es/ro/0dFRab">🍏</a></td>
</tr>
<tr>
<td>The Limiñanas – Malamore</td>
<td><a href="https://theliminanas.bandcamp.com/album/malamore">♫</a></td>
<td><a href="https://itun.es/ro/YEflbb">🍏</a></td>
</tr>
<tr>
<td>Lambchop – FLOTUS</td>
<td><a href="https://www.youtube.com/watch?v=DQMNeFnuyMU">♫</a></td>
<td><a href="https://itun.es/ro/qvEZdb">🍏</a></td>
</tr>
<tr>
<td>Loscil – Monument Builders</td>
<td><a href="https://loscil.bandcamp.com/album/monument-builders">♫</a></td>
<td><a href="https://itun.es/ro/fm9yfb">🍏</a></td>
</tr>
<tr>
<td>Lubomyr Melnyk – Illirion</td>
<td><a href="https://www.youtube.com/watch?v=NrNJBcvNjjA">♫</a></td>
<td><a href="https://itun.es/ro/TCSWbb">🍏</a></td>
</tr>
<tr>
<td><strong>Ludovico Einaudi – Elements (The Remixes) EP</strong></td>
<td><a href="https://www.youtube.com/watch?v=UFLrP55hZqs">♫</a></td>
<td><a href="https://itun.es/ro/PSiEab">🍏</a></td>
</tr>
<tr>
<td>Max Richter – Sleep (Remixes)</td>
<td><a href="https://www.youtube.com/watch?v=uoiQTKOJr5M">♫</a></td>
<td><a href="https://itun.es/ro/zBKwab">🍏</a></td>
</tr>
<tr>
<td>Melanie de Biasio – Blackened Cities</td>
<td><a href="https://melaniedebiasio.bandcamp.com/album/blackened-cities">♫</a></td>
<td><a href="https://www.youtube.com/watch?v=uoiQTKOJr5M">🍏</a></td>
</tr>
<tr>
<td>Mogwai – Atomic</td>
<td><a href="https://temporaryresidence.bandcamp.com/album/atomic">♫</a></td>
<td><a href="https://itun.es/ro/wmU9_">🍏</a></td>
</tr>
<tr>
<td><strong>Murcof × Vanessa Wagner – Statea</strong></td>
<td><a href="https://infine-rec.bandcamp.com/album/statea-cd-lp">♫</a></td>
<td><a href="https://itun.es/ro/aDJ4db">🍏</a></td>
</tr>
<tr>
<td><strong>Nick Cave & the Bad Seeds – Skeleton Tree</strong></td>
<td><a href="https://www.youtube.com/watch?v=BAMZYpZi_M4">♫</a></td>
<td><a href="https://itun.es/ro/7-wPcb">🍏</a></td>
</tr>
<tr>
<td>PJ Harvey – The Hope Six Demolition Project</td>
<td><a href="https://www.youtube.com/watch?v=qsLqsqbObyg">♫</a></td>
<td><a href="https://itun.es/ro/lO19_">🍏</a></td>
</tr>
<tr>
<td>Praed – The Fabrication of Silver Dreams</td>
<td><a href="https://soundcloud.com/annihaya-records/praed-pyramids-in-the-sky-fabrication-of-silver-dreams-2016">♫</a></td>
<td><a href="https://itun.es/ro/3Y1Cab">🍏</a></td>
</tr>
<tr>
<td><strong>Radiohead – A Moon Shaped Pool</strong></td>
<td><a href="https://www.youtube.com/watch?v=TTAU7lLDZYU">♫</a></td>
<td><a href="https://itun.es/ro/psvqcb">🍏</a></td>
</tr>
<tr>
<td>Richard J. Birkin – Vigils</td>
<td><a href="https://soundcloud.com/tomreveal/sets/richard-jbirkin-vigils-revealrecords">♫</a></td>
<td><a href="https://itun.es/ro/CF36_">🍏</a></td>
</tr>
<tr>
<td>Scott Walker – The Childhood of a Leader</td>
<td><a href="https://www.youtube.com/watch?v=drxpGP2r4UM">♫</a></td>
<td><a href="https://itun.es/ro/OPDfdb">🍏</a></td>
</tr>
<tr>
<td><strong>Tindersticks – The Waiting Room</strong></td>
<td><a href="https://tindersticks.bandcamp.com/album/the-waiting-room">♫</a></td>
<td><a href="https://itun.es/ro/J12t-">🍏</a></td>
</tr>
<tr>
<td>Trent Reznor & Atticus Ross – Juno</td>
<td>N/A</td>
<td><a href="https://itun.es/ro/0yLsdb">🍏</a></td>
</tr>
<tr>
<td>Trent Reznor & Atticus Ross, Gustavo Santaolalla, Mogwai – Before the Flood</td>
<td><a href="https://soundcloud.com/trentreznorandatticusross/sets/before-the-flood-soundtrack-1">♫</a></td>
<td><a href="https://itun.es/ro/u2Cufb">🍏</a></td>
</tr>
<tr>
<td>Various Artists – Eleven into Fifteen: a 130701 Compilation</td>
<td><a href="https://www.youtube.com/playlist?list=PLSjc8Z5Z5KGC9uASkjATYiWK7BecNeMcP">♫</a></td>
<td><a href="https://itun.es/ro/Lk_2fb">🍏</a></td>
</tr>
<tr>
<td>Various Artists – LateNightTales: Olafur Arnalds</td>
<td><a href="https://latenighttales.bandcamp.com/album/late-night-tales-lafur-arnalds">♫</a></td>
<td><a href="https://itun.es/ro/vTx-bb">🍏</a></td>
</tr>
<tr>
<td>Various Artists ‐ Pop Ambient 2017</td>
<td><a href="https://kompakt.bandcamp.com/album/pop-ambient-2017">♫</a></td>
<td><a href="https://itun.es/ro/U8Qefb">🍏</a></td>
</tr>
<tr>
<td>Wolfgang Voigt – Ambient Grunge</td>
<td><a href="https://www.youtube.com/watch?v=yzG9GyFzVYI">♫</a></td>
<td><a href="https://itun.es/ro/ynqUeb">🍏</a></td>
</tr>
<tr>
<td>Woodkid & Nils Frahm – Ellis EP</td>
<td><a href="https://soundcloud.com/erasedtapes/woodkid-nils-frahm-winter-morning-i">♫</a></td>
<td><a href="https://itun.es/ro/gxzQcb">🍏</a></td>
</tr>
<tr>
<td>Yann Tiersen – EUSA</td>
<td><a href="http://eusasound.bzh/">♫</a></td>
<td><a href="https://itun.es/ro/sPuQdb">🍏</a></td>
</tr>
</tbody>
</table>
<hr />
<h3>Addendum, Dec 16, 2016</h3>
<p>A great album that has cropped up since I've published the list is <a href="http://alkcm.bandcamp.com/album/jumping-the-shark">Alex Cameron's "Jumping the Shark"</a> (<a href="https://itun.es/ro/ahWGcb">🍏</a>). Also worth noting is <a href="https://www.youtube.com/watch?v=2roLC4yBmZU">Sylvan Esso's cover of "Everything is Free"</a> (<a href="https://itun.es/ro/EgKzeb?i=1147552350">🍏</a>) by Gillian Welch, whose album "Time (The Revelator)" (<a href="https://itun.es/ro/g6rWe">🍏</a>) was, well, a revelation.</p>
<p><strong>Bonus:</strong> This year's list in the form of <a href="https://www.youtube.com/playlist?list=PL3jn6K6l-pAHKqDH2zMNPWs6yi2gV5c3m">a glorious YouTube playlist</a>, courtesy of my friend Adi.</p>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>Other lists:</p>
<ul>
<li><a href="https://www.roughtrade.com/albums-of-the-year">Rough Trade</a></li>
<li><a href="http://thequietus.com/articles/21429-albums-of-the-year-2016">The Quietus</a></li>
</ul>
Extracting things from JavaScript strings2016-08-19T00:00:00Zhttps://danburzo.ro/string-extract/<p>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're invited to put a <code>RegExp.exec()</code> in a <code>while</code> loop. I'll show you a better if unorthodox way of doing it with <code>String.replace</code>.</p>
<h2>(Ab)using String.replace</h2>
<p>Let's start with the real-life-sounding, contrived scenario of wanting to <em>extract all numbers from a JavaScript string</em>. A simple pattern for matching all occurrences of numbers (specifically, decimal and floating point numbers) is:</p>
<pre class="language-js"><code class="language-js"><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[+-]?\d+(\.\d+)?</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span></code></pre>
<p>(don't forget the <code>g</code> flag for Global)</p>
<p>The <code>RegExp.exec</code> way of doing it:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> str <span class="token operator">=</span> <span class="token string">"Some of the best numbers are 42, and, in particular 42.999."</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> number_regex <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[+-]?\d+(\.\d+)?</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> matches <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> match<span class="token punctuation">;</span><br /><span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>match <span class="token operator">=</span> number_regex<span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> matches<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>match<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>matches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// => ["42", "42.999"]</span></code></pre>
<p>If instead we use <code>String.replace</code> we can write:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> str <span class="token operator">=</span> <span class="token string">"Some of the best numbers are 42, and, in particular 42.999."</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> number_regex <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[+-]?\d+(\.\d+)?</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> matches <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br />str<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>number_regex<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">match</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> matches<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>match<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>matches<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// => ["42", "42.999"]</span></code></pre>
<p>In effect, we're hijacking <code>String.replace</code> to act as an iterator over the matches, rather than actually replacing anything in the String.</p>
<p>This has a couple of advantages:</p>
<ol>
<li>you do away with the anxiety-inducing <code>while</code> loop because what can go wrong I'll tell you what can go wrong; use a regular expression literal in the <code>while</code> statement, instead of a regex saved in variable, and you'll get an infinite loop (courtesy of the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex"><code>lastIndex</code> property</a>):</li>
</ol>
<pre class="language-js"><code class="language-js"><span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>match <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[+-]?\d+(\.\d+)?</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token constant">FOREVERCODE</span> <span class="token punctuation">}</span></code></pre>
<ol start="2">
<li>you get to have <em>named parameters</em> for your matches instead indexes in an array.</li>
</ol>
<h2>Extracting parts of a pattern</h2>
<p>Each group we create in a regular expression, using the <code>(...)</code> construct, will result in an additional parameter to our "replacer" function. Let's take another example.</p>
<p>Assume in an blog article you can add Wordpress-style shortcodes for embedding videos:</p>
<pre class="language-txt"><code class="language-txt">Top 10 videos this months:<br /><br />1. [youtube:FyCsJAj69sc]<br />2. [vimeo:128373915]<br />...</code></pre>
<p>A straightforward regular expression to match these shortcodes is:</p>
<pre class="language-js"><code class="language-js"><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\[\w+:\w+\]</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><br /><br /><span class="token comment">// match alphanumeric characters, followed by colon, </span><br /><span class="token comment">// followed by another set of alphanumeric characters</span><br /><span class="token comment">// all wrapped in square brackets</span></code></pre>
<p>but we want to match the parts individually, so we put them in groups:</p>
<pre class="language-js"><code class="language-js"><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\[(\w+):(\w+)\]</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span></code></pre>
<p>Our pattern-extraction function now receives two extra parameters, one for each group:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> str <span class="token operator">=</span> <span class="token string">"Top 10 videos this months: \<br /> 1. [youtube:FyCsJAj69sc] \<br /> 2. [vimeo:128373915]"</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> shortcode_regex <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\[(\w+):(\w+)\]</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> matches <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br />str<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>shortcode_regex<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">match<span class="token punctuation">,</span> code<span class="token punctuation">,</span> id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> matches<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">code</span><span class="token operator">:</span> code<span class="token punctuation">,</span><br /> <span class="token literal-property property">id</span><span class="token operator">:</span> id<br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>matches<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>For which we get:</p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">[</span><br /> <span class="token punctuation">{</span><span class="token string-property property">"code"</span><span class="token operator">:</span> <span class="token string">"youtube"</span><span class="token punctuation">,</span> <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"FyCsJAj69sc"</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">{</span><span class="token string-property property">"code"</span><span class="token operator">:</span> <span class="token string">"vimeo"</span><span class="token punctuation">,</span> <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"128373915"</span><span class="token punctuation">}</span><br /><span class="token punctuation">]</span> </code></pre>
<p><strong>Tip:</strong> Have a regex group that you don't want to show up in the matcher function? Use the non-capturing group syntax <code>(?: ... )</code>.</p>
<hr />
<p>Now you know how to make pattern extraction more readable and less error-prone.</p>
A few FACT mixes2016-01-22T00:00:00Zhttps://danburzo.ro/fact-mixes/<p>Time for another listicle sans commentary!</p>
<p>Here are a few of the weirder <a href="http://www.factmag.com/category/factmixes/">FACT magazine mixes</a> — the ones to which I find myself getting back, time and time again.</p>
<h2>#379 - Grouper (Apr 2013)</h2>
<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/88974392&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe>
<p><a href="http://www.factmag.com/2013/04/22/fact-mix-379-grouper/">Link</a>. Tracklist N/A.</p>
<h2>#399 - Zola Jesus (Sept 2013)</h2>
<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/109494875&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe>
<p><a href="http://www.factmag.com/2013/09/09/fact-mix-399-zola-jesus/">Link</a>. Tracklist N/A.</p>
<h2>#414 - Julianna Barwick (Dec 2013)</h2>
<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/122803861&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe>
<p><a href="http://www.factmag.com/2013/12/02/fact-mix-414-julianna-barwick/">Link</a>. Tracklist N/A.</p>
<h2>#445 - Stephen O'Malley (Jun 2014)</h2>
<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/153536431&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe>
<p><a href="http://www.factmag.com/2014/06/09/fact-mix-445-stephen-omalley/">Link</a>.</p>
<h3>Tracklist</h3>
<ol>
<li>Spiridon Shisgigin - ‘Tanz der Schamanin (Dance of the Female Shaman)’ (from Soul of Yakutia)</li>
<li>Akira Rabelais - ‘Comme Un Ange Enivré D’un Soleil Radieux’ (from Caduceus)</li>
<li>Hornroh - ‘MUEZZO’ (from Findling)</li>
<li>Ernstalbrecht Stiebler - ‘…Im klang II’ (from «… Im klang…» hat(now))</li>
<li>Angus MacLise - ‘Universal Solar Calendar’ (The Cloud Doctrine)</li>
<li>Angus MacLise - ‘Tambura Drone + Sine Wave Generator’ (from The Cloud Doctrine)</li>
<li>Moluk Zarrābi Darāmad - ‘Dād (Māhur)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)</li>
<li>Montakhab-oz-Zākerin - ‘Qafqāz I (Segāh)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)</li>
<li>Montakhab-oz-Zākerin - ‘Qafqāz II (Segāh)’ (from Let No One Judge You: Early Recordings From Iran, 1906-1933)</li>
<li>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)</li>
<li>Hans Kennel, Mytha - ‘Chuehreiheli’ (from How It All Started)</li>
<li>Susan Alcorn - ‘Sapphire’ (from New Music For Old Instruments)</li>
<li>Christian Wolff - ‘Exercise 10’ (from 10 Exercises)</li>
<li>Arthur Brown’s Kingdom Come - ‘Triangles’ (from Journey)</li>
<li>Jacula - ‘Ego Sum Qui Sum’ (from Anno Demoni)</li>
<li>Antonius Rex - ‘Enchanted Woods’ (from Ralefun)</li>
<li>Fushitsusha - ‘暗号’ (from A Document Film Of Keiji Haino)</li>
<li>Joe Meek/The Blue Men - ‘The Bulblight’ (from I hear a new world)</li>
<li>François-Bernard Mâche - ‘Ouverture’ (from L’announce faite a marie)</li>
<li>François-Bernard Mâche - ‘Le baiser’ (from L’announce faite a marie)</li>
<li>François-Bernard Mâche - ‘Le vent’ (from L’announce faite a marie)</li>
<li>François-Bernard Mâche - ‘Chevauchée’ (from L’announce faite a marie)</li>
<li>François-Bernard Mâche - ‘Le pélerin’ (from L’announce faite a marie)</li>
<li>François-Bernard Mâche - ‘Ma mise au tombeau’ (from L’announce faite a marie)</li>
<li>Jakob Ullmann - ‘Disappearing Musics For Six Players (More Or Less)’ (from Edition Zeitgenössische Musik)</li>
</ol>
<h2>#471 - Jonny Trunk (Nov 2014)</h2>
<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/177643957&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe>
<p><a href="http://www.factmag.com/2014/11/20/fact-mix-470-jonny-trunk/">Link</a>.</p>
<h3>Tracklist</h3>
<ol>
<li>Guide To Oral Sex (unreleased)</li>
<li>You – Mean</li>
<li>Flugerenden – Unknown</li>
<li>Hot Water – Hazlewood / Trey</li>
<li>Pace – Golden Ring 6</li>
<li>Screw On – Jacky Giordano</li>
<li>The Sapphic Sleep – Don Cherry</li>
<li>Ode To A Screw – Taking Off</li>
<li>The Wizard – Frankie Bones</li>
<li>Metallic Doings – Bruton Library</li>
<li>Electro People – Fox</li>
<li>Moogies Bloogies – Anthony Newley / Delia Derbyshire (unreleased)</li>
<li>Shadows Of Blood – Platonos</li>
<li>Eyes Without A Face – Jarre</li>
<li>See Saw – Libby</li>
<li>Smilin Billy – Heath Bros</li>
<li>Even The Horses Had Wings – Kathy Bobo Bates</li>
<li>Underwater Boy – Lindt</li>
<li>Airlock – Herrmann</li>
<li>Makkaresh – Torrossi</li>
<li>Tornadeo – Jiants</li>
<li>Ghost Horse – Elsie</li>
<li>Sea Drift – Jonny Trunk (unreleased)</li>
</ol>
<h2>#512 - Max Richter</h2>
<iframe width="100%" height="166" src="https://www.mixcloud.com/widget/iframe/?autoplay=&embed_type=widget_standard&embed_uuid=bea7143a-1463-4fde-9048-214fb2e28a75&feed=%2FFACTMixArchive%2Ffact-mix-512-max-richter-sep-15%2F&hide_artwork=&hide_cover=&hide_tracklist=&light=&mini=&replace=0&stylecolor=" frameborder="0"></iframe>
<p><a href="http://www.factmag.com/2015/09/07/fact-mix-512-max-richter/">Link</a>.</p>
<h3>Tracklist</h3>
<ol>
<li>Charles Ives – The Unanswered Question</li>
<li>Godspeed You! Black Emperor – Rockets fall on Rocket falls</li>
<li>J.S. Bach – Fugue in C# minor from Book 1 of The Well tempered Clavier</li>
<li>J.S Bach – Chorale from “Christ Lag in Todesbanden”</li>
<li>Osvaldo Golijov – Tenebrae II</li>
<li>Urmas Sisask – Ursa Minor from Starry Sky Cycle</li>
<li>Boards of Canada – Over the horizon radar</li>
<li>Sergei Rachmaninov – Rejoice O Virgin from The Vespers</li>
<li>Howard Skempton – Of Late</li>
<li>Philip Glass – Violin Concerto 2nd Movement</li>
<li>Grouper – Clearing</li>
<li>Henry Purcell – Fantasia in 7 parts</li>
<li>William Byrd – Mass for 5 voices, Agnus Dei</li>
<li>Luciano Berio – Wasserklavier</li>
<li>Michael Tippet – Concerto for Sting Orchestra, II Adagio Cantabile</li>
<li>Cat Power – Maybe Not</li>
</ol>
<h2>#522 - Vainio & Vigroux</h2>
<iframe width="100%" height="166" src="https://www.mixcloud.com/widget/iframe/?autoplay=&embed_type=widget_standard&embed_uuid=05911972-4681-4292-999b-36b575424fcd&feed=%2FFACTMixArchive%2Ffact-mix-522-vainio-vigroux-nov-15%2F&hide_artwork=&hide_cover=&hide_tracklist=&light=&mini=&replace=0&stylecolor=" frameborder="0"></iframe>
<p><a href="http://www.factmag.com/2015/11/09/fact-mix-522-vainio-vigroux/">Link</a>.</p>
<h3>Tracklist</h3>
<ol>
<li>Giacinto Scelsi – Anahit</li>
<li>Orchestra and choir of Polish radio</li>
<li>Pharmakon – Crawling on bruised knees</li>
<li>Napalm Death – Peel Sessions 88: tracks 1 to 10</li>
<li>Bernard Parmegiani – Accidents Harmoniques – De Naturae Sonorum (GRM)</li>
<li>Scott Walker – Clara</li>
<li>Techno Animal – Hell</li>
<li>Federico Schmucher – Print…?</li>
<li>Whitehouse – Wriggle like a Fucking Eel</li>
<li>Swans – Coward</li>
<li>Robert de Visee – Courante</li>
<li>Interprète Pascal Monteilhet (Orbe) – Zig Zag label</li>
<li>Haino Keiji – First Blackness</li>
<li>Disques Du Soleil Et De L’Acier</li>
<li>Shapednoise feat. Justin K Broadrick – Enlightenment</li>
</ol>
<h2>#527 - Jóhann Jóhannsson (Dec 2015)</h2>
<iframe width="100%" height="166" src="https://www.mixcloud.com/widget/iframe/?autoplay=&embed_type=widget_standard&embed_uuid=fcd37e56-4e90-4a2e-bc82-90c829c16abe&feed=%2FFACTMixArchive%2Ffact-mix-527-j%25C3%25B3hann-j%25C3%25B3hannsson-dec-15%2F&hide_artwork=&hide_cover=&hide_tracklist=&light=&mini=&replace=0&stylecolor=" frameborder="0"></iframe>
<p><a href="http://www.factmag.com/2015/12/07/fact-mix-527-johann-johannsson/">Link</a>.</p>
<h3>Tracklist</h3>
<ol>
<li>Mihaly Vig: The Turin Horse</li>
<li>Dimitri Shostakovich: String Quartet No. 15 in E Flat Minor Op. 144 – 1. Elegy, Adagio</li>
<li>David Lang: Amelia (I’m Waiting for My Man)</li>
<li>Meredith Monk: Braid 1 and Leaping Song</li>
<li>Robert Turman: Flux</li>
<li>Glenn Branca: Second Movement (The Temple of Venus Pt. 2)</li>
<li>Gloria Coates: Chiaroscuro – Illumination</li>
<li>Witold Lutoslawski: Funeral Music – Prologue</li>
<li>Jón Leifs: Hinsta Kveðja (Elegy) Op. 53 for String Orchestra</li>
<li>Irena Havlová & Vojtěch Havel: Pod Hvězdou Pomiranč (Under The Orange Star)</li>
<li>Charlemagne Palestine: Sliding Fifths (1 2)</li>
<li>Alvin Lucier: I Am Sitting in a Room</li>
<li>Holger Czukay: Boat Woman Song</li>
<li>Charlemagne Palestine: One Two Three Fifths (1 3)</li>
</ol>
Drawing every street in Romania2015-12-28T00:00:00Zhttps://danburzo.ro/every-street/<p>Ben Fry's project <a href="http://3rdfloor.fathom.info/products/all-streets">All Streets</a> left quite the impression on me back in 2007 and it'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.</p>
<p>And this happened:</p>
<p><img src="https://danburzo.ro/img/streets.jpg" alt="Every Street" /></p>
<p>Here's a step-by-step account of how I got there.</p>
<hr />
<h2>1. Get the data</h2>
<p>Geofabrik thoughtfully packages <a href="http://download.geofabrik.de/europe.html">OpenStreetMap data</a> for every country, so I grabbed the <code>.osm.pbf</code> for Romania. <a href="http://wiki.openstreetmap.org/wiki/PBF_Format">PBF</a> is an alternative to the XML format in which OSM data is usually kept.</p>
<p>OSM works with just three data types:</p>
<ul>
<li><strong>nodes</strong> define points in space (through latitude & longitude);</li>
<li><strong>ways</strong> are collections of <em>nodes</em> which define linear features (yay, streets!) and area boundaries;</li>
<li><strong>relations</strong> are sometimes used to explain how other elements work together — e.g. multiple ways that define a longer route.</li>
</ul>
<p>For our modest purposes, we only need:</p>
<ol>
<li>Ways that are labeled as <em>streets</em>;</li>
<li>The nodes that comprise those ways.</li>
</ol>
<h2>2. Extracting street data from the PBF</h2>
<p>Time to brush off our Node.js skills and extract the data from the PBF file.</p>
<p>After a naïve attempt at loading everything in memory, it became apparent that data at this volume needs <a href="https://github.com/substack/stream-handbook">streaming</a> — a technique in which we read data item-by-item, with only fraction in memory at any given time. <a href="https://github.com/substack/osm-pbf-parser"><code>osm-pbf-parser</code></a> is a streaming parser which goes through the PBF data and outputs small sets of JSON objects in the formats below.</p>
<p><strong>This is a node...</strong></p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">{</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'node'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">122321</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">lat</span><span class="token operator">:</span> <span class="token number">53.527972600000005</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">lon</span><span class="token operator">:</span> <span class="token number">10.0241143</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">info</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><strong>...and this is a way</strong></p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">{</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'way'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">108</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">created_by</span><span class="token operator">:</span> <span class="token string">'Potlatch 0.8'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">highway</span><span class="token operator">:</span> <span class="token string">'living_street'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Kitzbühler Straße'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">postal_code</span><span class="token operator">:</span> <span class="token string">'01217'</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">refs</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token number">442752</span><span class="token punctuation">,</span> <span class="token number">231712390</span><span class="token punctuation">,</span> <span class="token number">442754</span> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">info</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Traversing the PBF file, we can do these checks to pick up the items we need:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">isNode</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> item<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'node'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">isStreet</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> item<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'way'</span> <span class="token operator">&&</span> item<span class="token punctuation">.</span>tags<span class="token punctuation">.</span>highway<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We'll put our extracted nodes and streets into plain-text files, with one item per line — this is a format that's amenable to streaming so it will be easy to read them back on subsequent steps.</p>
<p><strong>extract-nodes.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> osm_parser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'osm-pbf-parser'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> JSONStream <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'JSONStream'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'data/data.osm.pbf'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">OUTPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/nodes.txt'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">isNode</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> item<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'node'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">serializeNode</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> item<span class="token punctuation">.</span>id <span class="token operator">+</span> <span class="token string">','</span> <span class="token operator">+</span> item<span class="token punctuation">.</span>lat <span class="token operator">+</span> <span class="token string">','</span> <span class="token operator">+</span> item<span class="token punctuation">.</span>lon<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Extracting nodes from data file: '</span> <span class="token operator">+</span> <span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">osm_parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">items<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> nodes <span class="token operator">=</span> items<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>isNode<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> output <span class="token operator">=</span> nodes<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>serializeNode<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>output<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Finished extracting nodes onto file: '</span> <span class="token operator">+</span> <span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>extract-streets.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> osm_parser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'osm-pbf-parser'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> JSONStream <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'JSONStream'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'data/data.osm.pbf'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">OUTPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets.txt'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">isStreet</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> item<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'way'</span> <span class="token operator">&&</span> item<span class="token punctuation">.</span>tags<span class="token punctuation">.</span>highway<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">serializeStreet</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> item<span class="token punctuation">.</span>refs<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Extracting streets from data file: '</span> <span class="token operator">+</span> <span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">osm_parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">items<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> streets <span class="token operator">=</span> items<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>isStreet<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> output <span class="token operator">=</span> streets<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>serializeStreet<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>output<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Finished extracting streets onto file: '</span> <span class="token operator">+</span> <span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Which outputs:</p>
<p><strong>nodes.txt</strong></p>
<pre><code>360714, 44.493699500000005, 26.0854494
360853, 44.467436600000006, 26.0771428
537912, 44.425765000000006, 26.123137900000003
546140, 44.47436450000001, 26.123994300000003
...
</code></pre>
<p>(<code>id,latitude,longitude</code>)</p>
<p><strong>streets.txt</strong></p>
<pre><code>656951, 2260664460, 3227352565, 656952, ...
256700851, 2152136723, 659642, 256705252, 2152144026, ...
304797001, 2382014755, 310215524, 255848765, ...
</code></pre>
<p>(<code>node1,node2,node3,...</code>)</p>
<p><em>Note:</em> We're using <a href="https://github.com/rvagg/through2"><code>through2.obj()</code></a> to simplify the pipework.</p>
<h2>3. Mapping node IDs to their coordinates</h2>
<p>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:</p>
<ol>
<li>Load the nodes into some sort of database</li>
<li>Query the database to look up the coordinates for a given ID</li>
</ol>
<p>For storage I've turned to <a href="http://leveldb.org/">LevelDB</a> which is a pretty straightforward, file-based database. You use it in Node through <a href="https://github.com/Level/leveldown">leveldown</a> and <a href="https://github.com/Level/levelup">levelup</a>.</p>
<p><strong>load-nodes.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> split2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'split2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> levelup <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'level'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">DATABASE_NAME</span> <span class="token operator">=</span> <span class="token string">'everystreet'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/nodes.txt'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'creating levelDB database '</span> <span class="token operator">+</span> <span class="token constant">DATABASE_NAME</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">levelup</span><span class="token punctuation">(</span><span class="token constant">DATABASE_NAME</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> db</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> write_stream <span class="token operator">=</span> db<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">encoding</span><span class="token operator">:</span> <span class="token string">'utf8'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">split2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">line<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> parts <span class="token operator">=</span> line<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">key</span><span class="token operator">:</span> parts<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">value</span><span class="token operator">:</span> parts<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token string">','</span> <span class="token operator">+</span> parts<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// Prevent memory leak</span><br /> <span class="token comment">// See: https://github.com/rvagg/node-levelup/issues/298</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>i<span class="token operator">++</span> <span class="token operator">></span> <span class="token number">999</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">setImmediate</span><span class="token punctuation">(</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>write_stream<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><br /> <span class="token string">'Finished importing nodes into the database '</span> <span class="token operator">+</span> <span class="token constant">DATABASE_NAME</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This creates a LevelDB database with the name <code>everystreet</code> (which in turn creates an <code>everystreet</code> folder where the data is kept), and adds all nodes with <code>key=ID</code> and <code>value=lat,lon</code>.</p>
<p><em>Note:</em> While attempting this I <a href="https://github.com/rvagg/node-levelup/issues/298">ran into some memory troubles</a> to which the easy solution is to delay every 1000th <code>next()</code> call. There's also <a href="https://github.com/maxogden/level-bulk-load"><code>level-bulk-load</code></a> which attempts to optimize bulk writing in LevelDB, so that might be something to look into.</p>
<p>Next, let's map the node IDs to their coordinates in our street definitions.</p>
<p><strong>apply-nodes.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> split2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'split2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> levelup <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'level'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> async <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'async'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">DATABASE_NAME</span> <span class="token operator">=</span> <span class="token string">'everystreet'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets.txt'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">OUTPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets-with-coordinates.txt'</span><span class="token punctuation">;</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><br /> <span class="token string">'Applying node data from database '</span> <span class="token operator">+</span><br /> <span class="token constant">DATABASE_NAME</span> <span class="token operator">+</span><br /> <span class="token string">' to street data from file: '</span> <span class="token operator">+</span><br /> <span class="token constant">INPUT_FILE</span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">levelup</span><span class="token punctuation">(</span><span class="token constant">DATABASE_NAME</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> db</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> write_stream <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">encoding</span><span class="token operator">:</span> <span class="token string">'utf8'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">split2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">line<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> async<span class="token punctuation">.</span><span class="token function">mapSeries</span><span class="token punctuation">(</span><br /> line<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">node_id<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> db<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>node_id<span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> coords</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">callback</span><span class="token punctuation">(</span>err<span class="token punctuation">,</span> coords<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> result</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>write_stream<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><br /> <span class="token string">'Finished applying node data into file: '</span> <span class="token operator">+</span> <span class="token constant">OUTPUT_FILE</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>We're streaming through each street in the data file, querying the database for the coordonates — using <a href="http://promise-nuggets.github.io/articles/15-map-in-series.html"><code>async.mapSeries</code></a> to make sure we get back the node data in the correct order — and serializing them into a plain-text file.</p>
<p><strong>streets-with-coordinates.js</strong></p>
<pre><code>44.469672200000005, 26.093109000000002, 44.469469600000004, 26.093366600000003, ...
44.46975080000001, 26.092981700000003, 44.4696756, 26.092841000000004, ...
</code></pre>
<p>At this point we're done with extracting all the data we need but we still need to convert it from geographical coordinates to screen coordinates. <em>*Takes deep breath*</em>. Onwards!</p>
<h2>4. Mapping geographical coordintes to screen coordinates</h2>
<p>There are many <a href="http://bl.ocks.org/mbostock/3711652">different ways to project the Earth's surface</a> onto 2D space. Many maps are laid out based on the <a href="http://wiki.openstreetmap.org/wiki/Mercator">spherical Mercator projection</a>. Assuming <code>λ</code> is the longitude and <code>φ</code> is the latitude, both expressed in radians, the formula is simple:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">mercator</span><span class="token punctuation">(</span><span class="token parameter">λ<span class="token punctuation">,</span> φ</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span>λ<span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">tan</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token constant">PI</span> <span class="token operator">/</span> <span class="token number">4</span> <span class="token operator">+</span> φ <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Before we dive into it, let's see what we need to do:</p>
<ol>
<li>Find the <em>bounding box</em> of all our coordinates and its aspect ratio;</li>
<li>Transform the geographical coordinates into screen coordinates, based on the bounding box.</li>
</ol>
<p>The <em>bounding box</em> 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's <em>aspect ratio</em>, which we're going to use later. The script below computes both:</p>
<p><strong>bbox.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> split2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'split2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets-with-coordinates.txt'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">OUTPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/bbox.json'</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// initial values</span><br /><span class="token keyword">var</span> bbox <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">north</span><span class="token operator">:</span> <span class="token operator">-</span><span class="token number">90</span><span class="token punctuation">,</span> <span class="token comment">// minimum latitude</span><br /> <span class="token literal-property property">south</span><span class="token operator">:</span> <span class="token number">90</span><span class="token punctuation">,</span> <span class="token comment">// maximum latitude</span><br /> <span class="token literal-property property">east</span><span class="token operator">:</span> <span class="token operator">-</span><span class="token number">180</span><span class="token punctuation">,</span> <span class="token comment">// minimum longitude</span><br /> <span class="token literal-property property">west</span><span class="token operator">:</span> <span class="token number">180</span> <span class="token comment">// maximum longitude</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">toRadians</span><span class="token punctuation">(</span><span class="token parameter">deg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span>deg <span class="token operator">*</span> Math<span class="token punctuation">.</span><span class="token constant">PI</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">180</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">mercator</span><span class="token punctuation">(</span><span class="token parameter">λ<span class="token punctuation">,</span> φ</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span>λ<span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">tan</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token constant">PI</span> <span class="token operator">/</span> <span class="token number">4</span> <span class="token operator">+</span> φ <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">projection</span><span class="token punctuation">(</span><span class="token parameter">lat<span class="token punctuation">,</span> lon</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">mercator</span><span class="token punctuation">(</span><span class="token function">toRadians</span><span class="token punctuation">(</span>lon<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">toRadians</span><span class="token punctuation">(</span>lat<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Finding bounding box in file: '</span> <span class="token operator">+</span> <span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">encoding</span><span class="token operator">:</span> <span class="token string">'utf8'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">split2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">line<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> coords <span class="token operator">=</span> line<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator"><</span> coords<span class="token punctuation">.</span>length<span class="token punctuation">;</span> j <span class="token operator">+=</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> lat <span class="token operator">=</span> <span class="token function">parseFloat</span><span class="token punctuation">(</span>coords<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> lon <span class="token operator">=</span> <span class="token function">parseFloat</span><span class="token punctuation">(</span>coords<span class="token punctuation">[</span>j <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>lat <span class="token operator">></span> bbox<span class="token punctuation">.</span>north<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> bbox<span class="token punctuation">.</span>north <span class="token operator">=</span> lat<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>lat <span class="token operator"><</span> bbox<span class="token punctuation">.</span>south<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> bbox<span class="token punctuation">.</span>south <span class="token operator">=</span> lat<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>lon <span class="token operator">></span> bbox<span class="token punctuation">.</span>east<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> bbox<span class="token punctuation">.</span>east <span class="token operator">=</span> lon<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>lon <span class="token operator"><</span> bbox<span class="token punctuation">.</span>west<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> bbox<span class="token punctuation">.</span>west <span class="token operator">=</span> lon<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> nw_projected <span class="token operator">=</span> <span class="token function">projection</span><span class="token punctuation">(</span>bbox<span class="token punctuation">.</span>north<span class="token punctuation">,</span> bbox<span class="token punctuation">.</span>west<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> se_projected <span class="token operator">=</span> <span class="token function">projection</span><span class="token punctuation">(</span>bbox<span class="token punctuation">.</span>south<span class="token punctuation">,</span> bbox<span class="token punctuation">.</span>east<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">var</span> west <span class="token operator">=</span> nw_projected<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> north <span class="token operator">=</span> nw_projected<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> east <span class="token operator">=</span> se_projected<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> south <span class="token operator">=</span> se_projected<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">var</span> output <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">bbox</span><span class="token operator">:</span> bbox<span class="token punctuation">,</span><br /> <span class="token literal-property property">ratio</span><span class="token operator">:</span> <span class="token punctuation">(</span>east <span class="token operator">-</span> west<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span>north <span class="token operator">-</span> south<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> fs<span class="token punctuation">.</span><span class="token function">writeFile</span><span class="token punctuation">(</span><span class="token constant">OUTPUT_FILE</span><span class="token punctuation">,</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>output<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The script outputs the following information:</p>
<p><strong>bbox.json</strong></p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">{</span><br /> <span class="token string-property property">"bbox"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">"north"</span><span class="token operator">:</span> <span class="token number">48.4394855</span><span class="token punctuation">,</span><br /> <span class="token string-property property">"south"</span><span class="token operator">:</span> <span class="token number">43.578847700000004</span><span class="token punctuation">,</span><br /> <span class="token string-property property">"east"</span><span class="token operator">:</span> <span class="token number">29.726612400000004</span><span class="token punctuation">,</span><br /> <span class="token string-property property">"west"</span><span class="token operator">:</span> <span class="token number">20.198656500000002</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token string-property property">"ratio"</span><span class="token operator">:</span> <span class="token number">1.3601773494902782</span><br /><span class="token punctuation">}</span></code></pre>
<p>Let's now take our geographical coordinates transform them using the Mercator projection; afterwards, we express them as percentages within the map's bounding box which will make it easy for us to draw at any scale.</p>
<p><strong>map-coordinates.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> split2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'split2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets-with-coordinates.txt'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">OUTPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets-with-coordinates-mapped.txt'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">BBOX_FILE</span> <span class="token operator">=</span> <span class="token string">'output/bbox.json'</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// read the bounding box and project it using Mercator</span><br /><span class="token keyword">var</span> o <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span><span class="token constant">BBOX_FILE</span><span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> nw_projected <span class="token operator">=</span> <span class="token function">projection</span><span class="token punctuation">(</span>o<span class="token punctuation">.</span>bbox<span class="token punctuation">.</span>north<span class="token punctuation">,</span> o<span class="token punctuation">.</span>bbox<span class="token punctuation">.</span>west<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> se_projected <span class="token operator">=</span> <span class="token function">projection</span><span class="token punctuation">(</span>o<span class="token punctuation">.</span>bbox<span class="token punctuation">.</span>south<span class="token punctuation">,</span> o<span class="token punctuation">.</span>bbox<span class="token punctuation">.</span>east<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> north <span class="token operator">=</span> nw_projected<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> south <span class="token operator">=</span> se_projected<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> west <span class="token operator">=</span> nw_projected<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> east <span class="token operator">=</span> se_projected<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">toRadians</span><span class="token punctuation">(</span><span class="token parameter">deg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span>deg <span class="token operator">*</span> Math<span class="token punctuation">.</span><span class="token constant">PI</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">180</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">mercator</span><span class="token punctuation">(</span><span class="token parameter">λ<span class="token punctuation">,</span> φ</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span>λ<span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">tan</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token constant">PI</span> <span class="token operator">/</span> <span class="token number">4</span> <span class="token operator">+</span> φ <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">projection</span><span class="token punctuation">(</span><span class="token parameter">lat<span class="token punctuation">,</span> lon</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">mercator</span><span class="token punctuation">(</span><span class="token function">toRadians</span><span class="token punctuation">(</span>lon<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">toRadians</span><span class="token punctuation">(</span>lat<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function">percent</span><span class="token punctuation">(</span><span class="token parameter">lonlat</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">(</span>lonlat<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">-</span> west<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span>east <span class="token operator">-</span> west<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token punctuation">(</span>lonlat<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">-</span> south<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span>north <span class="token operator">-</span> south<span class="token punctuation">)</span><br /> <span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Mapping nodes using Mercador projection from file: '</span> <span class="token operator">+</span> <span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">encoding</span><span class="token operator">:</span> <span class="token string">'utf8'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">split2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">line<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> coords <span class="token operator">=</span> line<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">var</span> pts <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> coords<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i <span class="token operator">+=</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> pts<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><br /> <span class="token function">percent</span><span class="token punctuation">(</span><span class="token function">projection</span><span class="token punctuation">(</span>coords<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> coords<span class="token punctuation">[</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>pts<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">';'</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><br /> <span class="token string">'Finished mapping nodes using Mercator projection onto file: '</span> <span class="token operator">+</span><br /> <span class="token constant">OUTPUT_FILE</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>All nodes should be now in the <code>[0,1]</code> range:</p>
<p><strong>streets-with-coordinates-mapped.txt</strong></p>
<pre><code>0.6186481719547, 0.17686588159630384; 0.6186752081839509, 0.17682535252522288; ...
0.618634811271534, 0.1768816051533656; 0.6186200442006669, 0.17686656174973386; ...
...
</code></pre>
<h2>5. Drawing the map in SVG</h2>
<p>We now have everything we need to start drawing some SVG paths. This is the structure we're aiming for:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>svg</span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span> <span class="token attr-name">viewbox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M x1 y1 L x2 y2 L x3 y3 ...<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> ...<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>svg</span><span class="token punctuation">></span></span></code></pre>
<p>Remember the <em>map ratio</em> 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 <code>[0,1]</code> range to SVG-ready coordinates, we just need to factor in the map's dimensions:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> map_width <span class="token operator">=</span> <span class="token number">1500</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> map_height <span class="token operator">=</span> map_width <span class="token operator">/</span> map_ratio<span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> screen_x <span class="token operator">=</span> longitude <span class="token operator">*</span> map_width<span class="token punctuation">;</span><br /><span class="token keyword">var</span> screen_y <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> latitude<span class="token punctuation">)</span> <span class="token operator">*</span> map_height<span class="token punctuation">;</span></code></pre>
<p><em>Note:</em> The <code>1 - latitude</code> is to account for the fact that the origin of SVG coordinates is at the <em>top left</em> corner while our coordinates assume an origin in the <em>bottom left</em> corner.</p>
<p><strong>generate-svg.js</strong></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> through2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'through2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> split2 <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'split2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> multiline <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'multiline'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> <span class="token constant">DEFAULT_MAP_WIDTH</span> <span class="token operator">=</span> <span class="token number">1500</span><span class="token punctuation">;</span> <span class="token comment">// px</span><br /><br /><span class="token keyword">var</span> <span class="token constant">INPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets-with-coordinates-mapped.txt'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">OUTPUT_FILE</span> <span class="token operator">=</span> <span class="token string">'output/streets.svg'</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> <span class="token constant">BBOX_FILE</span> <span class="token operator">=</span> <span class="token string">'output/bbox.json'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> o <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span><span class="token constant">BBOX_FILE</span><span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> map_width <span class="token operator">=</span> <span class="token constant">DEFAULT_MAP_WIDTH</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> map_height <span class="token operator">=</span> map_width <span class="token operator">/</span> o<span class="token punctuation">.</span>ratio<span class="token punctuation">;</span><br /><br /><span class="token keyword">var</span> write_stream <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />write_stream<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><br /> <span class="token string">"<svg xmlns='http://www.w3.org/2000/svg' width='{w}' height='{h}' viewbox='0 0 {w} {h}'>"</span><br /> <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\{w\}</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span> map_width<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\{h\}</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span> map_height<span class="token punctuation">)</span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Generating SVG from file: '</span> <span class="token operator">+</span> <span class="token constant">INPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token constant">INPUT_FILE</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">encoding</span><span class="token operator">:</span> <span class="token string">'utf8'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">split2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><br /> through2<span class="token punctuation">.</span><span class="token function">obj</span><span class="token punctuation">(</span><br /> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">line<span class="token punctuation">,</span> enc<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> path_data <span class="token operator">=</span><br /> <span class="token string">'M '</span> <span class="token operator">+</span><br /> line<br /> <span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">';'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">pt</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> lonlat <span class="token operator">=</span> pt<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span><br /> lonlat<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">*</span> map_width<span class="token punctuation">,</span><br /> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> lonlat<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">*</span> map_height<br /> <span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">' L '</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><br /> <span class="token string">'<path d="'</span> <span class="token operator">+</span><br /> path_data <span class="token operator">+</span><br /> <span class="token string">'" stroke-width="0.1" stroke="black" fill="none"/>\n'</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">flush</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">'</svg>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>write_stream<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Finished generating SVG onto file: '</span> <span class="token operator">+</span> <span class="token constant">OUTPUT_FILE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And voilá! We have our all streets in Romania drawn up in SVG, which you can open in your browser (<a href="http://benfry.com/writing/archives/62">as opposed to other tools</a>).</p>
<p>Here it is in all its glory: <a href="http://danburzo.ro.s3-website-us-east-1.amazonaws.com/assets/streets.svg">streets.svg</a> <em>(Warning: 262MB file!)</em></p>
<h2>Final thoughts</h2>
<p><strong>Making it printable</strong>. Loading or converting a 262MB SVG is no easy feat, but ImageMagick somehow miraculously created a wall-sized PNG image:</p>
<pre><code>convert -density 900 output/streets.svg output/streets.png
</code></pre>
<p>Another idea worth pursuing is making <a href="https://github.com/atom/electron">electron</a> print out a PDF.</p>
<p><strong>Optimizing the map</strong>. Taking into account the output resolution, one could simplify the paths with <a href="http://mourner.github.io/simplify-js/">simplify.js</a> to eliminate details without affecting the appearance (e.g. points that are very close together):</p>
<pre class="language-js"><code class="language-js"><span class="token function">simplify</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token operator">...</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token operator">...</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">0.5</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token literal-property property">x</span> <span class="token operator">:</span> <span class="token operator">...</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">]</span></code></pre>
<p>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:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> path_buffer <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token operator">...</span><br /> path_buffer <span class="token operator">+=</span> path_data<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>counter<span class="token operator">++</span> <span class="token operator">></span> <span class="token number">999</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">'<path d="'</span> <span class="token operator">+</span> path_buffer <span class="token operator">+</span> <span class="token string">'" stroke-width="0.1" stroke="black" fill="none"/>\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> path_buffer <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span></code></pre>
<hr />
<p>I hope you've enjoyed this short foray into mapping! You can find all the scripts discussed here on Github: <a href="https://github.com/danburzo/every-street">danburzo/every-street</a>. If you have any idea on how to make this workflow better, <a href="https://github.com/danburzo/every-street/issues/new">I'd love to hear it</a>!</p>
Eight Versions of Satie’s Gnossienne No. 1 (1890)2015-12-26T00:00:00Zhttps://danburzo.ro/gnossienne-1/<h3>1. <a href="https://youtu.be/1FQ4EVMCSV0">Family Fodder - The Big Dig</a></h3>
<p><em>The Big Dig / Plant Life (1982)</em></p>
<h3>2. <a href="https://youtu.be/R0ACJfsUHnw">Daisaku Kume - Azuma No Theme</a></h3>
<p>From the sountrack of Takeshi Kitano's <a href="https://en.wikipedia.org/wiki/Violent_Cop_(1989_film)"><em>Violent Cop</em></a> (1989).</p>
<h3>3. <a href="https://www.youtube.com/watch?v=aUcb0B5ibtY">Jun Miyake - Gnossienne #1</a></h3>
<p><em>(Glam Exotica!, 1999)</em></p>
<h3>4. <a href="https://youtu.be/fQpUTUu0d34">Arthur H & Feist - La chanson de Satie</a></h3>
<p><em>(Adieu tristesse, 2005)</em></p>
<h3>5. <a href="https://www.youtube.com/watch?v=Hwyr6-X2OmY">Beltuner - Gnossienne #1</a></h3>
<p><em>(Beltuner, 2005)</em></p>
<h3>6. <a href="https://youtu.be/bHY2ldYdaKA">Chicha Libre - Gnossienne No. 1</a></h3>
<p><em>(¡Sonido Amazonico!, 2008)</em></p>
<h3>7. <a href="https://youtu.be/1d6-4Y9GnsE">Erkan Oğur - Gnossienne No. 1</a></h3>
<p><em>(Mommo OST, 2009)</em></p>
<h3>8. <a href="https://youtu.be/NlofCrcQIUw">Orange Blossom - Ya Sîdî</a></h3>
<p><em>(Under the Shade of Violets, 2014)</em></p>
<hr />
<p><a href="https://danburzo.ro/linked-erik-satie/">More Satie</a>.</p>
My favorite records from 20152015-12-18T00:00:00Zhttps://danburzo.ro/favorite-records-2015/<p>Great albums released this year, A-Z with <strong>favorites are in bold</strong>.</p>
<table>
<thead>
<tr>
<th>Album</th>
<th>♫</th>
<th>🍏</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Adam Bryanbaum Wiltzie – Travels in Constants Vol. 24</strong></td>
<td><a href="https://adambryanbaumwiltzie.bandcamp.com/album/travels-in-constants-vol-24">♫</a></td>
<td><a href="https://itun.es/ro/nipN7">🍏</a></td>
</tr>
<tr>
<td>Anthony Child – Electronic Recordings from Maui Jungle Vol. 1</td>
<td><a href="https://editionsmego.bandcamp.com/album/electronic-recordings-from-maui-jungle-vol-1">♫</a></td>
<td><a href="https://itun.es/ro/xx__9">🍏</a></td>
</tr>
<tr>
<td>Arca – Mutant</td>
<td><a href="https://soundcloud.com/arca1000000/soichiro">♫</a></td>
<td><a href="https://itun.es/ro/MmDE-">🍏</a></td>
</tr>
<tr>
<td>Bang on a Can All-Stars – Field Recordings</td>
<td><a href="https://bangonacan.bandcamp.com/album/field-recordings">♫</a></td>
<td><a href="https://itun.es/ro/Pgjc7">🍏</a></td>
</tr>
<tr>
<td>Beirut – No No No</td>
<td><a href="https://www.youtube.com/watch?v=G8lOkgyPcaU">♫</a></td>
<td><a href="https://itun.es/ro/C9DC7">🍏</a></td>
</tr>
<tr>
<td>Björk – Vulnicura</td>
<td><a href="https://www.youtube.com/watch?v=gQEyezu7G20">♫</a></td>
<td><a href="https://itun.es/ro/yBao5">🍏</a></td>
</tr>
<tr>
<td>Chilly Gonzales – Chambers</td>
<td><a href="https://soundcloud.com/chillygonzales/sets/chilly-gonzales-chambers">♫</a></td>
<td><a href="https://itun.es/ro/dJTy5">🍏</a></td>
</tr>
<tr>
<td><strong>Demdike Stare – Testpressing #007: Rathe/Patchwork</strong></td>
<td><a href="https://www.youtube.com/playlist?list=PLpOEGKO1FO70SGUZyGm-u7a4fXluvnJwm">♫</a></td>
<td><a href="https://itun.es/ro/yo5g8">🍏</a></td>
</tr>
<tr>
<td>Father John Misty – I Love You, Honeybear</td>
<td><a href="https://www.youtube.com/watch?v=caMfvhKIgBo">♫</a></td>
<td><a href="https://itun.es/ro/PVtO3">🍏</a></td>
</tr>
<tr>
<td>Four Tet – Morning/Evening</td>
<td><a href="https://fourtet.bandcamp.com/album/morning-evening">♫</a></td>
<td><a href="https://itun.es/ro/6-It8">🍏</a></td>
</tr>
<tr>
<td>Godspeed You! Black Emperor – 'Asunder, Sweet and Other Distress'</td>
<td><a href="http://cstrecords.com/cst111/">♫</a></td>
<td><a href="https://itun.es/ro/zH7L5">🍏</a></td>
</tr>
<tr>
<td>Grimes – Art Angels</td>
<td><a href="https://www.youtube.com/watch?v=Tv9YoYCKNoE">♫</a></td>
<td><a href="https://itun.es/ro/bzvP-">🍏</a></td>
</tr>
<tr>
<td><strong>Holly Herndon – Platform</strong></td>
<td><a href="https://hollyherndon.bandcamp.com/album/platform">♫</a></td>
<td><a href="https://itun.es/ro/Zjz95">🍏</a></td>
</tr>
<tr>
<td>Inventions – Maze of Woods</td>
<td><a href="https://inventions.bandcamp.com/album/maze-of-woods">♫</a></td>
<td><a href="https://itun.es/ro/R7Nq5">🍏</a></td>
</tr>
<tr>
<td>Jamie xx – In Colour</td>
<td><a href="https://www.youtube.com/watch?v=0fOHh5Q7Q1E">♫</a></td>
<td><a href="https://itun.es/ro/w6ju6">🍏</a></td>
</tr>
<tr>
<td>Jeff Bridges – Sleeping Tapes</td>
<td><a href="http://www.dreamingwithjeff.com/">♫</a></td>
<td><a href="https://itun.es/ro/Lkj_9">🍏</a></td>
</tr>
<tr>
<td>Jenny Hval – Apocalypse, girl</td>
<td><a href="https://jennyhval.bandcamp.com/album/apocalypse-girl">♫</a></td>
<td><a href="https://itun.es/ro/0ZeW5">🍏</a></td>
</tr>
<tr>
<td>Jim O'Rourke – Simple Songs</td>
<td><a href="http://www.dragcity.com/products/simple-songs">♫</a></td>
<td></td>
</tr>
<tr>
<td>Joanna Newsom – Divers</td>
<td><a href="https://www.youtube.com/watch?v=48xlgXqQKLA">♫</a></td>
<td></td>
</tr>
<tr>
<td><strong>Jon Hopkins – Late Night Tales</strong></td>
<td><a href="https://latenighttales.bandcamp.com/album/late-night-tales-jon-hopkins">♫</a></td>
<td><a href="https://itun.es/ro/Z1Mq9">🍏</a></td>
</tr>
<tr>
<td>Kurt Vile – b’lieve i’m goin down...</td>
<td><a href="https://www.youtube.com/watch?v=659pppwniXA">♫</a></td>
<td><a href="https://itun.es/ro/ZIgE8">🍏</a></td>
</tr>
<tr>
<td>Lubomyr Melnyk – Rivers and Streams</td>
<td><a href="https://soundcloud.com/erasedtapes/lubomyr-melnyk-parasol-excerpt">♫</a></td>
<td></td>
</tr>
<tr>
<td>Luke Abbott – Music For A Flat Landscape: Original Soundtrack to 'The Goob'</td>
<td><a href="https://lukeabbottmusic.bandcamp.com/album/music-for-a-flat-landscape-original-soundtrack-to-the-goob">♫</a></td>
<td><a href="https://itun.es/ro/Ozn36">🍏</a></td>
</tr>
<tr>
<td>METZ – II</td>
<td><a href="https://metz.bandcamp.com/album/ii">♫</a></td>
<td><a href="https://itun.es/ro/UG4s5">🍏</a></td>
</tr>
<tr>
<td><strong>Max Richter – From Sleep</strong></td>
<td><a href="https://www.youtube.com/watch?v=8dvpT0hA0Lk">♫</a></td>
<td><a href="https://itun.es/ro/TNux8">🍏</a></td>
</tr>
<tr>
<td><strong>Nava Mamă – Nava Mamă</strong></td>
<td><a href="https://navamama.bandcamp.com/album/nava-mam">♫</a></td>
<td></td>
</tr>
<tr>
<td><strong>Nils Frahm – Late Night Tales</strong></td>
<td><a href="https://latenighttales.bandcamp.com/album/late-night-tales-nils-frahm">♫</a></td>
<td><a href="https://itun.es/ro/IH7V9">🍏</a></td>
</tr>
<tr>
<td>Nils Frahm – Solo</td>
<td><a href="http://www.pianoday.org/">♫</a></td>
<td><a href="https://itun.es/ro/Zqkr6">🍏</a></td>
</tr>
<tr>
<td>Noveller – Fantastic Planet</td>
<td><a href="https://novellermusic.bandcamp.com/album/fantastic-planet">♫</a></td>
<td></td>
</tr>
<tr>
<td>Oneohtrix Point Never – Garden of Delete</td>
<td><a href="https://youtu.be/td-e4i2BL_Q">♫</a></td>
<td><a href="https://itun.es/ro/w-JU9">🍏</a></td>
</tr>
<tr>
<td>OZmotic & Fennesz – AirEffect</td>
<td><a href="https://www.youtube.com/watch?v=BkB9z2G67Kg">♫</a></td>
<td><a href="https://itun.es/ro/43948">🍏</a></td>
</tr>
<tr>
<td>Protomartyr – The Agent Intellect</td>
<td><a href="https://protomartyr.bandcamp.com/album/the-agent-intellect">♫</a></td>
<td><a href="https://itun.es/ro/qI9u8">🍏</a></td>
</tr>
<tr>
<td><strong>Rachel Grimes – The Clearing</strong></td>
<td><a href="https://rachelgrimes.bandcamp.com/album/the-clearing">♫</a></td>
<td><a href="https://itun.es/ro/u8T25">🍏</a></td>
</tr>
<tr>
<td>Richard Hawley – Hollow Meadows</td>
<td><a href="http://www.theguardian.com/music/musicblog/2015/sep/03/richard-hawley-hollow-meadows-exclusive-album-stream">♫</a></td>
<td><a href="https://itun.es/ro/0vvw8">🍏</a></td>
</tr>
<tr>
<td><strong>Richie Hawtin – From My Mind To Yours</strong></td>
<td><a href="https://plus8records.bandcamp.com/album/from-my-mind-to-yours">♫</a></td>
<td><a href="https://itun.es/ro/HXxk_">🍏</a></td>
</tr>
<tr>
<td>Sleaford Mods – Key Markets</td>
<td><a href="https://soundcloud.com/sleafordmods/face-to-faces">♫</a></td>
<td><a href="https://itun.es/ro/TU4y7">🍏</a></td>
</tr>
<tr>
<td>Sleater-Kinney – No Cities to Love</td>
<td><a href="https://sleaterkinney.bandcamp.com/album/no-cities-to-love">♫</a></td>
<td><a href="https://itun.es/ro/yk4q3">🍏</a></td>
</tr>
<tr>
<td>Squarepusher – Damogen Furies</td>
<td><a href="https://www.youtube.com/watch?v=-usL_kRXm6s">♫</a></td>
<td><a href="https://itun.es/ro/as7P5">🍏</a></td>
</tr>
<tr>
<td>St Germain – St Germain</td>
<td><a href="https://soundcloud.com/nonesuchrecords/st-germain-sittin-here-edit">♫</a></td>
<td><a href="https://itun.es/ro/Azn66">🍏</a></td>
</tr>
<tr>
<td>Sufjan Stevens – Carrie and Lowell</td>
<td><a href="http://music.sufjan.com/album/carrie-lowell">♫</a></td>
<td><a href="https://itun.es/ro/igo94">🍏</a></td>
</tr>
<tr>
<td>Sunn O))) – Kannon</td>
<td><a href="https://sunn.bandcamp.com/album/kannon">♫</a></td>
<td><a href="https://itun.es/ro/8og9-">🍏</a></td>
</tr>
<tr>
<td>Tame Impala – Currents</td>
<td><a href="https://youtu.be/hefh9dFnChY">♫</a></td>
<td><a href="https://itun.es/ro/uWw76">🍏</a></td>
</tr>
<tr>
<td>The Gurdjieff Ensemble, Levon Eskenian – Komitas</td>
<td><a href="https://www.ecmrecords.com/catalogue/1443525465">♫</a></td>
<td></td>
</tr>
<tr>
<td>Tigran Hamasyan – Luys i Luso</td>
<td><a href="https://www.ecmrecords.com/catalogue/1441200392">♫</a></td>
<td></td>
</tr>
<tr>
<td>Viet Cong – Viet Cong</td>
<td><a href="https://vietcong.bandcamp.com/album/viet-cong">♫</a></td>
<td><a href="https://itun.es/ro/e94r3">🍏</a></td>
</tr>
<tr>
<td>Wil Bolton – Marram</td>
<td><a href="https://wilbolton.bandcamp.com/album/marram">♫</a></td>
<td></td>
</tr>
<tr>
<td>William Basinski – The Deluge/Cascade</td>
<td><a href="https://williambasinski.bandcamp.com/album/the-deluge">♫</a></td>
<td><a href="https://itun.es/ro/5oZx6">🍏</a></td>
</tr>
<tr>
<td>Wolfgang Voigt – Rückverzauberung 10 / Nationalpark</td>
<td><a href="http://www.kompakt.fm/releases/rueckverzauberung_10_nationalpark">♫</a></td>
<td><a href="https://itun.es/ro/z9Po7">🍏</a></td>
</tr>
<tr>
<td>Yuta Nagashima – White Sleep</td>
<td><a href="https://darlamusic.bandcamp.com/album/white-sleep">♫</a></td>
<td><a href="https://itun.es/ro/vpDO4">🍏</a></td>
</tr>
</tbody>
</table>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
<p>A few other year-end lists:</p>
<ul>
<li><a href="http://www.roughtrade.com/aoty2015">Rough Trade</a></li>
<li><a href="http://thequietus.com/articles/19350-best-albums-2015">The Quietus</a></li>
<li><a href="http://www.factmag.com/2015/12/09/the-50-best-albums-of-2015/">FACT</a></li>
<li><a href="http://the-attic.net/features/1616/the-attic:-favorite-albums-of-2015.html">The Attic</a></li>
</ul>
<p>+ Largehearted Boy's <a href="http://www.largeheartedboy.com/blog/archive/2015/11/essential_and_i_1.html">excellent meta-list</a>.</p>
Walking in Walden2015-01-20T00:00:00Zhttps://danburzo.ro/walden-walk/<p>Every sentence in <a href="http://en.wikipedia.org/wiki/Walden"><em>Walden</em></a> that contains the words <em>I</em> and <em>walk</em>.</p>
<hr />
<p>"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.</p>
<p>"I walked about the outside, at first unobserved from within, the window was so deep and high.</p>
<p>"I never in all my walks came across a man engaged in so simple and natural an occupation as building his house.</p>
<p>"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.</p>
<p>"I walked over each farmer'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.</p>
<p>"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.</p>
<p>"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.</p>
<p>"As I walked in the woods to see the birds and squirrels, so I walked in the village to see the men and boys.</p>
<p>"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.</p>
<p>"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.</p>
<p>"Once I was surprised to see a cat walking along the stony shore of the pond, for they rarely wander so far from home.</p>
<p>"I sometimes left a good fire when I went to take a walk in a winter afternoon.</p>
<p>"For many weeks I met no one in my walks but those who came occasionally to cut wood and sled it to the village.</p>
<p>"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. " I felt it, and still remark it almost daily in my walks, for by it hangs the history of a family.</p>
<p>"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.</p>
<p>"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.</p>
<p>"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.</p>
<p>"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.</p>
<p>"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.</p>
<p>"The ice in the pond at length begins to be honeycombed, and I can set my heel in it as I walk.</p>
<p>"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."</p>
<hr />
<p>I extracted the text by running this on the <a href="http://www.gutenberg.org/files/205/205-h/205-h.htm">HTML version from Project Gutenberg</a>:</p>
<pre class="language-js"><code class="language-js"><span class="token class-name">Array</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">slice</span><br /> <span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'p'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">p</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> p<span class="token punctuation">.</span>textContent<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">paragraph</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> paragraph<br /> <span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[\.\?\!\;]\s*</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">sentence</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> sentence<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'walk'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <br /> <span class="token operator">&&</span> sentence<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'I '</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">sentences</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> sentences<span class="token punctuation">.</span>length<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">sentences</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> sentences<br /> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'. '</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\n</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\s+</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span> <span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'.\n\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Further reading:</h3>
<p>Darius Kazemi's note on <a href="http://tinysubversions.com/notes/aphorism-detection/">aphorism detection</a>, especially this:</p>
<blockquote>
<p>Let'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.</p>
</blockquote>
<p>Poet Robert Fitterman went about doing <a href="http://www.poets.org/poetsorg/poem/hemingway-reader-sun-also-also-rises">something similar by hand</a>:</p>
<blockquote>
<p>When I was 13, my brother gave me a copy of Hemingway’s <em>The Sun Also Rises</em>. 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’.</p>
</blockquote>
<p>And Thoreau did write an essay <a href="http://www.gutenberg.org/files/1022/1022-h/1022-h.htm">on walking</a>.</p>
My favorite records from 20142015-01-13T00:00:00Zhttps://danburzo.ro/favorite-records-2014/<p>In no particular order:</p>
<table>
<thead>
<tr>
<th>Album</th>
<th>♫</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mogwai – Rave Tapes</td>
<td><a href="https://www.subpop.com/releases/mogwai/rave_tapes">♫</a></td>
</tr>
<tr>
<td>A Winged Victory for the Sullen – Atomos</td>
<td><a href="https://soundcloud.com/awvfts/atomos-vi-edit">♫</a></td>
</tr>
<tr>
<td>Scott Walker + Sunn O))) – Soused</td>
<td><a href="http://www.4ad.com/videos/387">♫</a></td>
</tr>
<tr>
<td>Sharon Van Etten – Are We There</td>
<td><a href="https://soundcloud.com/jagjaguwar/sharon-van-etten-every-time-the-sun-comes-up">♫</a></td>
</tr>
<tr>
<td>This Will Destroy You – Another Language</td>
<td><a href="https://thiswilldestroyyou.bandcamp.com/album/another-language">♫</a></td>
</tr>
<tr>
<td>Fennesz – Bécs</td>
<td><a href="https://soundcloud.com/forcedexposurepublicity/01-static-kings">♫</a></td>
</tr>
<tr>
<td>Flying Lotus – You're Dead!</td>
<td><a href="https://soundcloud.com/flyinglotus/coronus-the-terminator">♫</a></td>
</tr>
<tr>
<td>Caribou – Our Love</td>
<td><a href="https://soundcloud.com/caribouband/cant-do-without-you">♫</a></td>
</tr>
<tr>
<td>Blonde Redhead – Barragán</td>
<td><a href="http://blonde-redhead.com/barragan/">♫</a></td>
</tr>
<tr>
<td>Aphex Twin – Syro</td>
<td><a href="https://soundcloud.com/warp-records/aphex-twin-minipops-67-1202source-field-mix">♫</a></td>
</tr>
<tr>
<td>Timber Timbre – Hot Dreams</td>
<td><a href="https://soundcloud.com/artsandcrafts/hot-dreams-1">♫</a></td>
</tr>
<tr>
<td>The War on Drugs – Lost in the Dream</td>
<td><a href="https://soundcloud.com/secretlycanadian/the-war-on-drugs-red-eyes">♫</a></td>
</tr>
<tr>
<td>Plaid – Reachy Prints</td>
<td><a href="https://soundcloud.com/plaid/hawkmoth">♫</a></td>
</tr>
<tr>
<td>Amen Dunes – Love</td>
<td><a href="https://soundcloud.com/sacredbones/amen-dunes-lonely-richard">♫</a></td>
</tr>
<tr>
<td>Strand of Oaks – HEAL</td>
<td><a href="https://soundcloud.com/deadoceans/strand-of-oaks-goshen-97-1">♫</a></td>
</tr>
<tr>
<td>Grouper – Ruins</td>
<td><a href="https://soundcloud.com/kranky/grouper-call-across-rooms">♫</a></td>
</tr>
<tr>
<td>Jon Hopkins – Asleep Versions</td>
<td><a href="https://www.youtube.com/watch?v=QECYg_6rM58">♫</a></td>
</tr>
<tr>
<td>Ben Frost – A U R O R A</td>
<td><a href="https://benfrost.bandcamp.com/album/a-u-r-o-r-a">♫</a></td>
</tr>
<tr>
<td>Jóhann Jóhannsson – McCanick OST</td>
<td><a href="https://soundcloud.com/milanrecords/j-hann-j-hannsson-payphone">♫</a></td>
</tr>
<tr>
<td>Leyland Kirby – We drink to forget the coming storm</td>
<td></td>
</tr>
<tr>
<td>Vivaldi Recomposed by Max Richter (reissue)</td>
<td><a href="https://soundcloud.com/max-richter/sets/recomposed-by-max-richter">♫</a></td>
</tr>
<tr>
<td>Steve Reich – Radio Rewrite</td>
<td><a href="http://www.nonesuch.com/albums/radio-rewrite">♫</a></td>
</tr>
<tr>
<td>Inventions – Inventions</td>
<td><a href="https://soundcloud.com/temporary-residence-ltd-1/inventions-entity">♫</a></td>
</tr>
<tr>
<td>Lisa Gerrard – Twilight Kingdom</td>
<td><a href="http://lisagerrard.com/store/twilightkingdom.html">♫</a></td>
</tr>
<tr>
<td>Locust – After the Rain</td>
<td><a href="https://soundcloud.com/editionsmego/locust-ill-be-there-emego-205">♫</a></td>
</tr>
<tr>
<td>Kyle Bobby Dunn – Kyle Bobby Dunn and the Infinite Sadness</td>
<td><a href="https://soundcloud.com/studentsofdecay/sets/kyle-bobby-dunn-infinite-sadness">♫</a></td>
</tr>
<tr>
<td>Various Artists – Pop Ambient 2015</td>
<td><a href="http://www.kompakt.fm/releases/pop_ambient_2015">♫</a></td>
</tr>
<tr>
<td>Gazelle Twin – UNFLESH</td>
<td><a href="https://gazelletwin.bandcamp.com/album/unflesh">♫</a></td>
</tr>
<tr>
<td>Rhyton – Kykeon</td>
<td><a href="https://soundcloud.com/thrilljockey/rhyton-topkapi">♫</a></td>
</tr>
<tr>
<td>Andy Stott – Faith In Strangers</td>
<td><a href="https://soundcloud.com/modernlove/andy-stott-faith-in-strangers">♫</a></td>
</tr>
<tr>
<td>Neel – Phobos</td>
<td><a href="https://soundcloud.com/spectrumspools/life-on-laputa-regio-by-neel">♫</a></td>
</tr>
<tr>
<td>Lubomyr Melnyk – Evertina</td>
<td><a href="https://soundcloud.com/erasedtapes/lubomyr-melnyk-evertina">♫</a></td>
</tr>
<tr>
<td>Tujiko Noriko – My Ghost Comes Back</td>
<td><a href="https://soundcloud.com/editionsmego/05-through-the-rain">♫</a></td>
</tr>
<tr>
<td>Hildur Guðnadóttir – Saman</td>
<td><a href="https://touch333.bandcamp.com/album/saman">♫</a></td>
</tr>
<tr>
<td>Mike Weis – Don't Know, Just Walk</td>
<td><a href="https://zelienople.bandcamp.com/album/dont-know-just-walk-2">♫</a></td>
</tr>
<tr>
<td>Philip Corner – Satie Slowly</td>
<td><a href="https://unseenworlds.bandcamp.com/album/satie-slowly">♫</a></td>
</tr>
<tr>
<td>Klara Lewis – Ett</td>
<td><a href="https://soundcloud.com/editionsmego/klara-lewis-shine-emego-190">♫</a></td>
</tr>
<tr>
<td>Wovenhand – Refractory Obdurate</td>
<td><a href="https://deathwishinc.bandcamp.com/album/refractory-obdurate">♫</a></td>
</tr>
</tbody>
</table>
<p>+ a couple of year-end lists to catch up with:</p>
<ul>
<li><a href="http://thequietus.com/articles/16739-albums-of-the-year-2014">The Quietus</a></li>
<li><a href="http://www.roughtrade.com/aoty2014">Rough Trade</a></li>
</ul>
<hr />
<p>Timeline of favorite records: <a href="https://danburzo.ro/favorite-records-2014/">2014</a>, <a href="https://danburzo.ro/favorite-records-2015/">2015</a>, <a href="https://danburzo.ro/favorite-records-2016/">2016</a>, <a href="https://danburzo.ro/favorite-records-2017/">2017</a>, <a href="https://danburzo.ro/favorite-records-2018/">2018</a>, <a href="https://danburzo.ro/favorite-records-2019/">2019</a>, <a href="https://danburzo.ro/favorite-records-2020/">2020</a>, <a href="https://danburzo.ro/favorite-records-2021/">2021</a>, <a href="https://danburzo.ro/favorite-records-2022/">2022</a>, <a href="https://danburzo.ro/favorite-records-2023/">2023</a>.</p>
Recently: Oldies2014-11-07T00:00:00Zhttps://danburzo.ro/recently-ii/<h3>Reading</h3>
<p>Simon Reynold's <em><a href="http://retromaniabysimonreynolds.blogspot.com/">Retromania: Pop Culture's Addiction To Its Own Past</a></em> explores the recent phenomenon of near-past nostalgia.</p>
<h3>Listening</h3>
<p>I got the <em><a href="http://en.wikipedia.org/wiki/The_Beatles_in_Mono">Beatles in Mono</a></em> version of <em>Revolver</em> the other day, in part because it was there in the bookstore but also because I love <em><a href="https://www.youtube.com/watch?v=7xL1ffMlzKY">Tomorrow Never Knows</a></em>, which you might remember from, you know, <a href="https://www.youtube.com/watch?v=p5NX1FC-7-w">every Chemical Brothers song</a>, <a href="https://www.youtube.com/watch?v=s5FyfQDO5g0">ever</a>.</p>
<p>A note inside the sleeve reads:</p>
<blockquote>
<p>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.</p>
</blockquote>
<p>It's just an incredible album. Oh and the fact that it's universally recognized as one of the essential records of the 20th century makes its <a href="http://en.wikipedia.org/wiki/Revolver_%28Beatles_album%29">track-by-track, meticulously researched Wikipedia page</a> an exquisite read.</p>
<hr />
<img src="https://danburzo.ro/img/sons-of-the-wind.jpg" width="400" />
<p>On the same trip I got <em><a href="http://soundwalkcollective.com/index.php?/sons-of-the-wind/">Sons of the Wind</a></em>, "a sound journey along the Danube [featuring] recordings from Gypsy villages in Ukraine, Moldavia, Romania, Bulgaria, Macedonia, Serbia, Hungary, Slovakia, Austria". Its haunting cover (shown above) stopped me in my tracks and ultimately convinced me to give it a listen despite not knowing what'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.</p>
<p><a href="http://vimeo.com/73966560">This video</a> by Soundwalk Collective gives you an idea:</p>
<div class="vimeo">
<iframe src="https://player.vimeo.com/video/73966560?title=0&byline=0&portrait=0" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
</div>
<p>There's also a <a href="http://www.virginieluc.com/ecouter/fils-du-vent/">three-part radio essay</a> by Virginie Luc, who wrote the texts for the project.</p>
Linked: Erik Satie2014-10-24T00:00:00Zhttps://danburzo.ro/linked-erik-satie/<p class="preamble"><em>Linked</em> is where I rummage through my notes and bookmarks for bits and bobs around a subject.</p>
<blockquote>
<p>"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."</p>
<p>— Erik Satie, <em>A Day in the Life of a Musician</em> (via <a href="http://www.ubu.com/papers/satie_day.html">UbuWeb</a>)</p>
</blockquote>
<p>Erik Satie was fond of invented musical taxonomy: <em><a href="http://en.wikipedia.org/wiki/Gnossiennes">gnossiennes</a></em>, <em><a href="http://en.wikipedia.org/wiki/Gymnop%C3%A9dies">gymnopédies</a></em>, <em><a href="http://en.wikipedia.org/wiki/Vexations">vexations</a></em>, <em><a href="http://en.wikipedia.org/wiki/Ogives">ogives</a></em>. His concept of <em>furniture music</em> was essential to the development of minimalist and ambient music. And <a href="http://www.press.umich.edu/8718/elevator_music">shitty elevator music</a>.</p>
<p><a href="https://www.youtube.com/watch?v=ErtNBlFpxWM">Branka Parlić's unhurried pace</a> — what I imagine Satie meant when he instructed the performer to play the pieces <em>lent et douloureux</em> (slowly, sadly, gravely) — has pretty much ruined other renditions for me: they come off as sloppy and hasty in comparison. I've probably listened to her album <em>Initiés '88 - Initiés '99</em> a hundred times. Sadly, it's been out of print for a while, so no chance to get it in a physical format. Next, listen to <a href="https://www.youtube.com/watch?v=_hMw1C6fPt8">her play piano works by Philip Glass</a> to convince yourself you should <a href="https://shop.klicktrack.com/438117">buy her latest album</a>.</p>
<p><em>Eisoptrophobia</em> translates as "fear of, or aversion to, reflections", the light version of which has possibly been impressed upon me by the seriously unsettling <a href="https://www.youtube.com/watch?v=fEJBdXUYqwA">mirror scene in the film <em>Below</em></a> that resulted in this ominous undertone to any mirror gazing ever since. But it's also the name of <a href="http://www.akirarabelais.com/vi/o/m/e/album.html">an album by Akira Rabelais</a> who crafts distorted, shimmering pieces based on <a href="http://www.akirarabelais.com/vi/o/m/e/c/08.mp3">music by Satie</a>, <a href="http://www.akirarabelais.com/vi/o/m/e/c/19.mp3">Bartók</a> and <a href="http://www.akirarabelais.com/vi/o/m/e/c/05.mp3">Carté</a>.</p>
<blockquote>
<p>"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."</p>
<p>— Jean Cocteau</p>
</blockquote>
<p>Further down the distorsion spectrum, we've got Bionulor a.k.a. Sebastian Banaszczyk's <em><a href="http://bionulor.bandcamp.com/album/erik">Erik</a></em>, a reworking of Satie's <em>Gymnopédies</em> that <a href="http://thequietus.com/articles/13587-polish-music-album-reviews">Luke Turner accurately describes</a> as "the sound of a hangover inside Brian Eno's splendid dome after he's had a terrible sherry-fuelled row with William Basinski..."</p>
<iframe style="border: 0; width: 75%; height: 400px;" src="https://bandcamp.com/EmbeddedPlayer/album=1077134846/size=large/bgcol=ffffff/linkcol=0687f5/artwork=small/transparent=true/" seamless=""><a href="http://bionulor.bandcamp.com/album/erik">Erik by Bionulor</a></iframe>
<p>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 <em><a href="http://www.arbouserecordings.com/view-release.php?id=36">Erik Satie et les nouveaux jeunes</a></em>. We've got Max Richter, Rachel Grimes, Sylvain Chauveau, Hauschka, Eluvium, Dustin O'Halloran, Nils Frahm, all the cool kids. The original is out of print, but <a href="http://arbouserecordings.bandcamp.com/album/erik-satie-et-les-nouveaux-jeunes-version-2">some sort of reissue is going on</a>. Fingers crossed!</p>
<iframe style="border: 0; width: 75%; height: 200px;" src="https://bandcamp.com/EmbeddedPlayer/album=3351296327/size=large/bgcol=ffffff/linkcol=333333/artwork=small/track=451635917/transparent=true/" seamless=""><a href="http://arbouserecordings.bandcamp.com/album/erik-satie-et-les-nouveaux-jeunes-version-2">Erik Satie et les nouveaux jeunes version 2 by Arbouse Recordings</a></iframe>
Recently: living in cold places2014-10-22T00:00:00Zhttps://danburzo.ro/recently-i/<h3>Reading</h3>
<p><em><a href="http://www.amazon.co.uk/Consolations-Forest-Alone-Cabin-Middle/dp/0141975474">Consolations of the Forest</a>: Alone in a Cabin in the Middle Taiga</em> by Sylvain Tesson, a journaled account of the six months he lived in a small cabin next to lake Baikal in Siberia. I'm reading it in Romanian but the English version and has a <a href="http://penguindesign.tumblr.com/post/51715097970/consolations-of-the-forest-alone-in-a-cabin-in">much nicer cover</a>.</p>
<p>Thinking about expanses of snow and ice reminded me of <a href="http://www.themorningnews.org/article/twilight-on-the-tundra">this essay from The Morning News</a> about a dog sled race across the tundras of Russia.</p>
<p>And on a completely different scale, the Lykov family <a href="http://www.smithsonianmag.com/history/for-40-years-this-russian-family-was-cut-off-from-all-human-contact-unaware-of-world-war-ii-7354256">spent 40 years in complete isolation</a> before being discovered in 1978 by a group of geologists. Agafia Lykov, the last surviving member of the family, <a href="https://www.youtube.com/watch?v=tt2AYafET68">still lives there</a>.</p>
<h3>Watching</h3>
<p><a href="http://en.wikipedia.org/wiki/Happy_People:_A_Year_in_the_Taiga"><em>Happy People: A Year in the Taiga</em></a>, 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's book. (It was.)</p>
<h3>Listening</h3>
<p><a href="http://scott-o.com/"><em>Soused</em></a>, a collaboration between Scott Walker and Sun O))) sounds like boiling tar, anvils and nightmare fuel. In other words, it's perfect and I can't stop listening to it.</p>
<p><a href="http://www.erasedtapes.com/store/index/ERATP061"><em>Atomos</em></a> 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.</p>
<p>Flying Lotus' latest offering, <a href="http://flying-lotus.com/youre-dead/"><em>You're Dead!</em></a>, is denser than his previous album but maintains the latter's dreamlike haze and submerged thumps.</p>
<p>Now seems like the perfect time to give Zola Jesus' <a href="http://en.wikipedia.org/wiki/Taiga_%28Zola_Jesus_album%29"><em>Taiga</em></a> a listen.</p>
How to create a SVG icon system2014-10-17T00:00:00Zhttps://danburzo.ro/svg-icon-system/<p>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.</p>
<p>The gist:</p>
<ol>
<li>Keep your icons in a single .AI file with multiple artboards, and export each artboard as SVG;</li>
<li>Use <code>grunt-svgstore</code> to clean up and merge the SVG files and <code>grunt-svginjector</code> to generate a JavaScript file that you can use to inject the SVGs into your page;</li>
<li>Write minimal CSS to lay out and color your icons;</li>
<li>Enjoy the awesomeness of crisp, responsive icons.</li>
</ol>
<p>Let's do this.</p>
<h2>The case for SVG icons</h2>
<p>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't cover it here, but <a href="http://css-tricks.com/svg-symbol-good-choice-icons/">Chris Coyier's article</a> explains well why we want to define our icons as named <code><symbol></code> elements that we can then reference like this:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>svg</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>icon<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>use</span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#icon-iconName<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>svg</span><span class="token punctuation">></span></span></code></pre>
<h2>Design your icons</h2>
<p>To work on your icons side by side and have them neatly packaged in a single .AI file, <em>use multiple artboards</em>--one for each icon in your set.</p>
<p>In the <kbd>New Document</kbd> screen, enter the number of artboards and choose other sensible settings (e.g. <kbd>✓ Align New Objects to Pixel Grid</kbd>)</p>
<p>In the <kbd>Artboards</kbd> pane (if it's not shown, go in and check <kbd>Window → ✓ Artboards</kbd>) 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.</p>
<h3>Pro tip interlude</h3>
<p>Whenever possible, it's a good idea to make your icon <em>a single compound path</em>, which basically takes the various <code><path></code>, <code><rect></code>, <code><polygon></code> etc. elements and merges them into a single <code><path></code>. This gives you a slimmer SVG and you'll avoid Firefox rendering quirks — I'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.</p>
<p>In Illustrator, you create a compound path by selecting your shapes and hitting <kbd>⌘8</kbd> on your keyboard. Use <kbd>⌥⇧⌘8</kbd> to release a compound path if you find you need to make changes to individual paths. Both actions are accessible under <kbd>Object → Compound Path</kbd>.</p>
<h3>Export time!</h3>
<p>Fast-forward to the moment you're happy with the set of icons and/or working inside Adobe Illustrator becomes unbearable and it's time to export those babies as separate SVG files. Granted it's a bit obtuse, since we piggyback off the <em>Save a copy...</em> functionality to obtain that which might more accurately be described as an export, but I haven't found a more straightforward way to do it:</p>
<p><kbd>File → Save a copy… → Format: SVG (svg), Use artboards: All</kbd></p>
<p>On the <kbd>SVG Options</kbd> screen the default settings (i.e. <kbd>Profile: SVG 1.1</kbd>) are fine. Each artboard will be saved as a separate SVG file with the name <kbd>fileName_artboardName.svg</kbd>.</p>
<h2>Clean up and package the icons</h2>
<p>Next, we take all the little individual SVG files and package them into a single SVG file. For that we will need <a href="http://gruntjs.com/">Grunt</a> and a plugin called <a href="https://github.com/FWeinb/grunt-svgstore">grunt-svgstore</a>. If you haven't used Grunt before, you can check out <a href="http://danburzo.ro/grunt/chapters/getting-started">this short introduction</a>.</p>
<p>Here's how our Gruntfile should look:</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">grunt</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> grunt<span class="token punctuation">.</span><span class="token function">loadNpmTasks</span><span class="token punctuation">(</span><span class="token string">'grunt-svgstore'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> grunt<span class="token punctuation">.</span><span class="token function">initConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">svgstore</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">icons</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">files</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">'icons.svg'</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'icons/*.svg'</span><span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token comment">/*<br /> prefix all icons with an unambiguous label<br /> */</span><br /> <span class="token literal-property property">prefix</span><span class="token operator">:</span> <span class="token string">'icon-'</span><span class="token punctuation">,</span><br /><br /> <span class="token comment">/* <br /> cleans fill, stroke, stroke-width attributes <br /> so that we can style them from CSS<br /> */</span><br /> <span class="token literal-property property">cleanup</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /><br /> <span class="token comment">/*<br /> write a custom function to strip the first part<br /> of the file that Adobe Illustrator generates <br /> when exporting the artboards to SVG<br /> */</span><br /> <span class="token function-variable function">convertNameToId</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> name<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^\w+\_</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Next we need a way to include the <code>icons.svg</code> file in our web page. Rather than copying the content and inlining it in our HTML, we can include it via a <code><script></code> tag (making it easier to maintain and a candidate for browser caching), for which we can use <a href="https://github.com/danburzo/archived/tree/main/grunt-svginjector">grunt-svginjector</a>:</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">grunt</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> grunt<span class="token punctuation">.</span><span class="token function">loadNpmTasks</span><span class="token punctuation">(</span><span class="token string">'grunt-svgstore'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> grunt<span class="token punctuation">.</span><span class="token function">loadNpmTasks</span><span class="token punctuation">(</span><span class="token string">'grunt-svginjector'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> grunt<span class="token punctuation">.</span><span class="token function">initConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">svgstore</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">icons</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">files</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">'icons.svg'</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'icons/*.svg'</span><span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">cleanup</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token function-variable function">convertNameToId</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> name<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^\w+\_</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">svginjector</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">icons</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">files</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">'icons.js'</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'icons.svg'</span><span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">container</span><span class="token operator">:</span> <span class="token string">'icon-container'</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> grunt<span class="token punctuation">.</span><span class="token function">registerTask</span><span class="token punctuation">(</span><span class="token string">'icons'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'svgstore:icons'</span><span class="token punctuation">,</span> <span class="token string">'svginjector:icons'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>In our HTML we need a container into which we'll inject the content of <code>icons.svg</code> and then we can just include the script:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>icon-container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>icons.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre>
<p><strong>Note:</strong> Since the script inserts the content immediately, you must make sure the script is always included <em>after</em> the container in your DOM.</p>
<p>We've registered a task called <code>icons</code> to execute the svgstore and svginjector steps in order, so we can run:</p>
<pre class="language-bash"><code class="language-bash">$ grunt icons</code></pre>
<p>...to prep our icons for usage.</p>
<h2>Use and customize your icons</h2>
<p>Some final touches and we're done. Firstly, we need to hide the icon container so it does not take space in the layout.</p>
<p>Remember that we set <code>cleanup: true</code> in <code>grunt-svgstore</code> to strip all presentational attributes like <code>fill</code>, <code>stroke</code> or <code>stroke-width</code>. This is a necessity if we want to control these attributes from our CSS, since no attribute defined inside the <code><symbol></code> elements can be overridden when we <code><use></code> them. To make sure everything looks okay, we'll define a stroke and a fill for the icons.</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* <br /> Hide the icon container, because the injected SVG <br /> takes up physical space.<br />*/</span><br /><span class="token selector">#icon-container</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">/*<br /> Set up the size, layout and colors.<br />*/</span><br /><span class="token selector">.icon</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span><br /> <span class="token property">width</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span><br /> <span class="token property">height</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.icon use</span> <span class="token punctuation">{</span><br /> <span class="token property">stroke</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span><br /> <span class="token property">fill</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Then, as promised, whenever you need to use an icon on your web page:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>svg</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>icon<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>use</span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#icon-myicon<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>svg</span><span class="token punctuation">></span></span></code></pre>
<p>Easy.</p>
<h2>Further reading</h2>
<ul>
<li>Chris Coyier's <a href="http://css-tricks.com/mega-list-svg-information/">A Compendium of SVG Information</a> has got you covered: see section <em>SVG as an Icon System</em>;</li>
<li><a href="https://github.com/willianjusten/awesome-svg#icons">awesome-svg</a> is a Github repository of links to SVG-related articles and resources.</li>
</ul>