<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://ryanccn.dev/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ryan Cao</title>
    <link>https://ryanccn.dev/</link>
    <atom:link href="https://ryanccn.dev/feed/rss.xml" rel="self" type="application/rss+xml" />
    <description>Ramblings about software development, privacy, and whatever happens to strike my fancy.</description>
    <language>en</language>
    <item>
      <title>Kimi Antonelli and Titanium Dioxide: A Quantitative Analysis</title>
      <link>https://ryanccn.dev/posts/kimi-antonelli-titanium-dioxide/</link>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This paper is available for &lt;a href=&quot;/images/kimi-antonelli-titanium-dioxide/paper.pdf&quot;&gt;download as a PDF&lt;/a&gt;. It might look more convincing.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;abstract&quot; tabindex=&quot;-1&quot;&gt;Abstract&lt;/h2&gt;
&lt;p&gt;Over the 2025 Formula One season, Andrea Kimi Antonelli, a driver for Mercedes-AMG Petronas Formula One Team, was observed to perform better in jurisdictions where titanium dioxide is a legal food additive. Using finishing position data for Grands Prix (including sprints) during the 2025 season, we found that Kimi Antonelli does finish significantly higher in races held in jurisdictions where titanium dioxide is a legal food additive; this effect is statistically significant both before and after normalization against the average constructor performance for Mercedes across Grands Prix. However, we did not find any statistical correlation between Antonelli’s fastest lap time during qualifying and titanium dioxide’s legal status, regardless of normalization against the team average. In addition, Antonelli’s generally lackluster performance at races situated in Europe, combined with the European Union’s ban on the use of titanium dioxide as a food additive, suggests that there may be other confounding variables involved in this correlational relationship.&lt;/p&gt;
&lt;h2 id=&quot;introduction&quot; tabindex=&quot;-1&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Andrea Kimi Antonelli is an Italian racing driver who has been competing in Formula One for Mercedes-AMG Petronas Formula One Team since the 2025 season and is currently the championship leader for the 2026 season. As the first race this year in a jurisdiction that does not allow the use of titanium dioxide as a food additive, the Monaco Grand Prix, draws ever closer, it was of great interest to us to review one of the myths that had been circulating during last year’s season: whether Kimi Antonelli performs better at races in jurisdictions where it is legally allowed for titanium dioxide to be used as a food additive.&lt;/p&gt;
&lt;h2 id=&quot;methodology&quot; tabindex=&quot;-1&quot;&gt;Methodology&lt;/h2&gt;
&lt;p&gt;Using data from various sources, we compiled a comprehensive dataset of the 24 Grands Prix held over the 2025 Formula One season (6 of which included sprints, for a total of 30 races). The dataset includes the legal status of titanium dioxide as a food additive in each of the jurisdictions the Grands Prix were held in, the finishing positions of Kimi Antonelli and George Russell in each race, and whether the race was held in the European Union (in which titanium dioxide is not allowed to be used as a food additive). Titanium dioxide’s legal status in Azerbaijan was unclear at the time of writing, so it is labelled as 0.5; DNFs are attributed a finishing position of 21 for ease of comparison.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Table 1&lt;/summary&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;race&lt;/th&gt;
&lt;th&gt;tio2&lt;/th&gt;
&lt;th&gt;antonelli&lt;/th&gt;
&lt;th&gt;russell&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AUS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHN-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JPN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BHR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIA-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EMI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ESP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CAN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AUT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GBR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BEL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BEL-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HUN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NED&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ITA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AZE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SIN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;USA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;USA-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MXC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LVG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QAT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QAT-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ABU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;p&gt;We also collected data on the fastest laps put in by Antonelli and Russell, as well as the pole lap, for each race during the 2025 Formula One season. The data are in units of milliseconds.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Table 2&lt;/summary&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;race&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;antonelli_quali&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;russell_quali&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;pole_quali&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AUS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;76525&lt;/td&gt;
&lt;td&gt;75546&lt;/td&gt;
&lt;td&gt;75096&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;91103&lt;/td&gt;
&lt;td&gt;90723&lt;/td&gt;
&lt;td&gt;90641&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHN-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;91738&lt;/td&gt;
&lt;td&gt;91169&lt;/td&gt;
&lt;td&gt;90849&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JPN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;87555&lt;/td&gt;
&lt;td&gt;87318&lt;/td&gt;
&lt;td&gt;86983&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BHR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;90213&lt;/td&gt;
&lt;td&gt;90009&lt;/td&gt;
&lt;td&gt;89841&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;87866&lt;/td&gt;
&lt;td&gt;87407&lt;/td&gt;
&lt;td&gt;87294&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;86271&lt;/td&gt;
&lt;td&gt;86385&lt;/td&gt;
&lt;td&gt;86204&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIA-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;86482&lt;/td&gt;
&lt;td&gt;86791&lt;/td&gt;
&lt;td&gt;86482&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EMI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;75772&lt;/td&gt;
&lt;td&gt;74807&lt;/td&gt;
&lt;td&gt;74670&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;71880&lt;/td&gt;
&lt;td&gt;71507&lt;/td&gt;
&lt;td&gt;69954&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ESP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;72111&lt;/td&gt;
&lt;td&gt;71848&lt;/td&gt;
&lt;td&gt;71546&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CAN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;71391&lt;/td&gt;
&lt;td&gt;70899&lt;/td&gt;
&lt;td&gt;70899&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AUT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;65276&lt;/td&gt;
&lt;td&gt;64763&lt;/td&gt;
&lt;td&gt;63971&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GBR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85374&lt;/td&gt;
&lt;td&gt;85029&lt;/td&gt;
&lt;td&gt;84892&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BEL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;102139&lt;/td&gt;
&lt;td&gt;101260&lt;/td&gt;
&lt;td&gt;100562&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BEL-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;105394&lt;/td&gt;
&lt;td&gt;102330&lt;/td&gt;
&lt;td&gt;100510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HUN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;76386&lt;/td&gt;
&lt;td&gt;75425&lt;/td&gt;
&lt;td&gt;75372&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NED&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;69493&lt;/td&gt;
&lt;td&gt;69255&lt;/td&gt;
&lt;td&gt;68662&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ITA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;79200&lt;/td&gt;
&lt;td&gt;79157&lt;/td&gt;
&lt;td&gt;78792&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AZE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;101717&lt;/td&gt;
&lt;td&gt;102070&lt;/td&gt;
&lt;td&gt;101117&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SIN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;89537&lt;/td&gt;
&lt;td&gt;89158&lt;/td&gt;
&lt;td&gt;89158&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;USA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;93114&lt;/td&gt;
&lt;td&gt;92826&lt;/td&gt;
&lt;td&gt;92510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;USA-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;94018&lt;/td&gt;
&lt;td&gt;92888&lt;/td&gt;
&lt;td&gt;92143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MXC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;76118&lt;/td&gt;
&lt;td&gt;76034&lt;/td&gt;
&lt;td&gt;75586&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;69685&lt;/td&gt;
&lt;td&gt;69942&lt;/td&gt;
&lt;td&gt;69511&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;69340&lt;/td&gt;
&lt;td&gt;69495&lt;/td&gt;
&lt;td&gt;69243&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LVG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;116314&lt;/td&gt;
&lt;td&gt;108803&lt;/td&gt;
&lt;td&gt;107934&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QAT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;79846&lt;/td&gt;
&lt;td&gt;79662&lt;/td&gt;
&lt;td&gt;79387&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QAT-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80532&lt;/td&gt;
&lt;td&gt;80087&lt;/td&gt;
&lt;td&gt;80055&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ABU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;83080&lt;/td&gt;
&lt;td&gt;82645&lt;/td&gt;
&lt;td&gt;82207&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;p&gt;In order to measure the effects of additional confounding variables, we also included in the dataset whether races took place inside the European Union, where titanium dioxide as a food additive is banned.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Table 3&lt;/summary&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;race&lt;/th&gt;
&lt;th&gt;is_european_union&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AUS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHN-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JPN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BHR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIA-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EMI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ESP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CAN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AUT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GBR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BEL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BEL-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HUN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NED&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ITA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AZE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SIN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;USA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;USA-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MXC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LVG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QAT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QAT-S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ABU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;h2 id=&quot;results&quot; tabindex=&quot;-1&quot;&gt;Results&lt;/h2&gt;
&lt;p&gt;Given this data, we fit a linear model against the relationship between Kimi Antonelli’s finishing position and the legal status of titanium dioxide as a food additive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/kimi-antonelli-titanium-dioxide/plot1.svg&quot; alt=&quot;Kimi Antonelli Finishing Position and Legal Status of Titanium Dioxide as Food Additive&quot;&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Dependent variable:&lt;/em&gt; &lt;strong&gt;antonelli&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tio2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-7.100*** (1.993)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;13.502*** (1.421)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observations&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;0.312&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adjusted R2&lt;/td&gt;
&lt;td&gt;0.287&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual Std. Error&lt;/td&gt;
&lt;td&gt;5.362 (df = 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F Statistic&lt;/td&gt;
&lt;td&gt;12.696*** (df = 1; 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;*p&amp;#x3C;0.1; **p&amp;#x3C;0.05; ***p&amp;#x3C;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We found that Kimi Antonelli’s finishing position in races during the 2025 Formula One season is strongly and negatively correlated against the legal status of titanium dioxide as a food additive, with a significance level of p&amp;#x3C;0.01.&lt;/p&gt;
&lt;p&gt;In order to account for general differences in constructor performance across different circuits, we also normalized Antonelli’s finishing position against the overall team performance for Mercedes across circuits by calculating the delta between Antonelli’s finishing position and the average of Antonelli’s and Russell’s finishing positions (i.e. &lt;code&gt;antonelli - ((antonelli + russell) / 2)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/kimi-antonelli-titanium-dioxide/plot2.svg&quot; alt=&quot;Kimi Antonelli Finishing Position (Relative to Mercedes Average) and Legal Status of Titanium Dioxide as Food Additive&quot;&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Dependent variable:&lt;/em&gt; antonelli_norm&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tio2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-2.772*** (0.826)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;4.082*** (0.589)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observations&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;0.287&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adjusted R2&lt;/td&gt;
&lt;td&gt;0.261&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual Std. Error&lt;/td&gt;
&lt;td&gt;2.223 (df = 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F Statistic&lt;/td&gt;
&lt;td&gt;11.257*** (df = 1; 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;*p&amp;#x3C;0.1; **p&amp;#x3C;0.05; ***p&amp;#x3C;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Again, we found that Kimi Antonelli’s finishing position in races, even when normalized against Mercedes’ team performance as a whole, is strongly and negatively correlated against titanium dioxide’s legal status as a food additive, with a significance level of p&amp;#x3C;0.01.&lt;/p&gt;
&lt;p&gt;Rather than using the qualifying position as an indicator of raw pace, as some prior literature on this topic has done, we chose to use fastest lap time during qualifying as a proportion of pole lap time as a better indicator of relative pace. Qualifying position itself is a data point that, in our view, is already reflected in race finishing position. However, it is to be noted that due to differing tyre allocations during different parts of qualifying and track evolution, it may not be on the whole completely appropriate to compare the fastest lap for a particular driver with the fastest lap overall in the context of a qualifying session.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/kimi-antonelli-titanium-dioxide/plot3.svg&quot; alt=&quot;Kimi Antonelli Qualifying Pace and Legal Status of Titanium Dioxide as Food Additive&quot;&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Dependent variable:&lt;/em&gt; antonelli_quali/pole_quali&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tio2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.003 (0.006)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;1.014*** (0.004)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observations&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;0.007&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adjusted R2&lt;/td&gt;
&lt;td&gt;-0.029&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual Std. Error&lt;/td&gt;
&lt;td&gt;0.016 (df = 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F Statistic&lt;/td&gt;
&lt;td&gt;0.195 (df = 1; 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;*p&amp;#x3C;0.1; **p&amp;#x3C;0.05; ***p&amp;#x3C;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Counter to our expectations, Antonelli’s qualifying pace did not have any statistically significant correlation with the legal status of titanium dioxide as a food additive. We then normalized this qualifying pace data against the average for Mercedes (i.e. &lt;code&gt;(antonelli_quali / pole_quali) - (((antonelli_quali / pole_quali) + (russell_quali / pole_quali)) / 2)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/kimi-antonelli-titanium-dioxide/plot4.svg&quot; alt=&quot;Kimi Antonelli Qualifying Pace (Relative to Mercedes Average) and Legal Status of Titanium Dioxide as Food Additive&quot;&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Dependent variable:&lt;/em&gt; antonelli_quali_norm&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tio2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.0001 (0.003)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;0.004* (0.002)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observations&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;0.00005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adjusted R2&lt;/td&gt;
&lt;td&gt;-0.036&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual Std. Error&lt;/td&gt;
&lt;td&gt;0.007 (df = 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F Statistic&lt;/td&gt;
&lt;td&gt;0.001 (df = 1; 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;*p&amp;#x3C;0.1; **p&amp;#x3C;0.05; ***p&amp;#x3C;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;After normalization, we still did not find any statistically significant relationship between Antonelli’s qualifying pace and the legal status of titanium dioxide as a food additive. These conclusions suggested to us that, if there was any causal relationship between Kimi Antonelli’s performance during Formula One sessions and whether titanium dioxide was legally allowed as a food additive, it was restricted to race pace only and not qualifying pace.&lt;/p&gt;
&lt;p&gt;Recognizing the fact that Kimi Antonelli generally did not perform particularly well at the European races during the 2025 season compared to his overall performance throughout the year, and the fact that the European Union, which includes many of the jurisdictions the European races were held in, has a blanket ban on the use of titanium dioxide as a food additive, we considered being in the European Union a confounding variable in the relationship between Antonelli’s F1 performance and titanium dioxide’s legal status.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Dependent variable:&lt;/em&gt; antonelli&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Dependent variable:&lt;/em&gt; tio2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;is_european_union&lt;/td&gt;
&lt;td&gt;8.920*** (2.069)&lt;/td&gt;
&lt;td&gt;-0.705*** (0.162)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;7.455*** (1.069)&lt;/td&gt;
&lt;td&gt;0.705*** (0.084)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observations&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;0.399&lt;/td&gt;
&lt;td&gt;0.402&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adjusted R2&lt;/td&gt;
&lt;td&gt;0.377&lt;/td&gt;
&lt;td&gt;0.381&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual Std. Error&lt;/td&gt;
&lt;td&gt;5.012 (df = 28)&lt;/td&gt;
&lt;td&gt;0.393 (df = 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F Statistic&lt;/td&gt;
&lt;td&gt;18.585*** (df = 1; 28)&lt;/td&gt;
&lt;td&gt;18.833*** (df = 1; 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;*p&amp;#x3C;0.1; **p&amp;#x3C;0.05; ***p&amp;#x3C;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Conducting linear model fitting for both of these variables against membership of the EU, we found that both Antonelli’s performance and the legal status of titanium dioxide had a statistically significant relationship with EU membership. It is possible, therefore, that there are other causes that could explain Antonelli’s relative poor performance in Europe during the middle part of the 2025 season that are not related to titanium dioxide not being allowed as a food additive, including specific circuit characteristics and Antonelli’s own ongoing development as a Formula One driver.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Using data on finishing positions and qualifying lap times during the 2025 Formula One World Championship as provided by the FIA and conducting linear model regressions against the legal status of titanium dioxide as a food additive, we found that Antonelli finished in statistically higher positions at races taking place in jurisdictions where the use of titanium dioxide as a food additive was legally allowed, both before and after normalization for average constructor performance across circuits. We did not, however, find any statistically significant relationship between Antonelli’s qualifying pace and the legal status of titanium dioxide regardless of normalization. If culinary use of titanium dioxide was causally linked to Antonelli’s racing performance, it is only race pace and not qualifying pace that is affected. In addition, it is possible that other factors may be able to explain Antonelli’s poor performance in Europe during the 2025 season that are unrelated to titanium dioxide’s legal status, due to the confounding effect of the EU’s general ban on the use of titanium dioxide as an additive.&lt;/p&gt;
&lt;h2 id=&quot;recommendations&quot; tabindex=&quot;-1&quot;&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;Despite the uncertainty surrounding the causality between the use of titanium dioxide as a food additive and Antonelli’s racing performance in F1, due to the non-zero possibility of Oscar Piastri, a highly talented driver racing for McLaren Mastercard F1 Team, making a miraculous comeback in the remainder of the 2026 season and winning his first WDC against all odds, it is our recommendation that Mercedes-AMG Petronas Formula One Team supply Kimi Antonelli with a titanium dioxide-heavy diet if they wish to secure the 2026 World Drivers’ Championship for Antonelli.&lt;/p&gt;
&lt;p&gt;Further research on this topic should consider expanding the dataset to include drivers from other teams as well and possibly conducting a controlled dietary experiment, which would result in more credible results and recommendations to drivers and constructors on how to best adjust for titanium dioxide availability across different races. It may also be beneficial for the FIA to look into banning titanium dioxide intake for drivers as part of the new set of regulations introduced in 2026 in order to ensure fairness and improve racing, if there are further studies that justify doing so.&lt;/p&gt;
</description>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
      <dc:creator>Ryan Cao</dc:creator>
      <guid>https://ryanccn.dev/posts/kimi-antonelli-titanium-dioxide/</guid>
    </item>
    <item>
      <title>Five Years of Design</title>
      <link>https://ryanccn.dev/posts/five-years-of-design/</link>
      <description>&lt;p&gt;In September, I had the privilege of being able to attend &lt;a href=&quot;https://2025.nixcon.org/&quot;&gt;NixCon 2025&lt;/a&gt; in Rapperswil-Jona, Switzerland. Aside from listening to a lot of fun and/or interesting talks about Nix (&lt;a href=&quot;https://www.youtube.com/watch?v=Dwop0jb_SO4&quot;&gt;“You can’t spell ‘devshell’ without ‘hell’”&lt;/a&gt; was one of my favorites) and meeting some of my friends in real life for the first time, I also had the wonderful opportunity to observe Swiss graphic, and especially typographic, design in action. This experience started with &lt;a href=&quot;https://www.zuerich.com/en&quot;&gt;Zürich Tourism’s website&lt;/a&gt;; it left a lasting impression on my mind, since it was one of first usages of Helvetica-adjacent neo-grotesque sans-serif fonts that looked good in my opinion. The use of various colors and font weights made the font (a custom one; “Zurich Haas”) an essential part of the website’s character.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/zuerich.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/zuerich.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/zuerich.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/zuerich.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/zuerich.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/zuerich.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/zuerich.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/zuerich.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/zuerich.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/zuerich.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/zuerich.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/zuerich.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/zuerich.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/zuerich.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/zuerich.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/zuerich.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/zuerich.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/zuerich.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/zuerich.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/zuerich.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/zuerich.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/zuerich.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/zuerich.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/zuerich.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/zuerich.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;A screenshot of Zürich Tourism&amp;#x27;s website&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAABjElEQVQYlWP4%2F%2F%2F%2F33%2F%2Fnn3%2B9ev3n3%2F%2F%2Fv3HAP%2FA6Nfv3xA5hge3b106f3bLjj1r1q7bu2%2F%2F4aMnDh09efT46eMnTp86fe7a9bv37t6%2FffPGnVs3Ht%2B78%2BzhfYb6gvy8hJiy9NSEsIiM%2BNiClKSqnKzW8pL2sqKpjTVbliw8vX394bWL969avGfFgu0LZzJsntG5Z9GULTM69szrO7iw%2F%2FSq6adXzzy9eub13asPr1l8bNW8OwfW39q%2F4eSGyRd39N88tJqhvTytvjx504JJO5bPWTWjf2ZX2fSuiqUzpu5cv3LT0qWLJ05aO2fa%2BlmT2svjspKdm8vzGAw0pJSV%2BaKD%2FWJCQ5OiY6NCPIP9naJCQ8ODAkL9fWyN9S31dS31dU0MNFVUJHQ0FRjkxYQ0FMWqyop8XR2lhYW0FOSUpaU0FeU1FKTV5CWUpMQVJcUlBfllREVlxUXEBHgY%2BHl5hPl5jU3MFJVUBYRERcVEpSUkpCWlJcUktJXkZWQVpGXlhYVFhUSEBYT4BXg5AABJwjCt9KmaAAAAAElFTkSuQmCC%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;In Switzerland, the same style of font appeared everywhere: wordmarks on trains, signage in train and tram stations, posters, and even receipts. What struck me most was the prominent, almost blatant placement of text, often in exceptionally large sizes, as the central element in a design. This stylistic choice conveys information very clearly to viewers without distracting elements, and fully demonstrates the inherent elegant design of the font as well — a utilitarian design, but beautiful in its utilitarianism.&lt;/p&gt;
&lt;div class=&quot;not-prose ry-gallery&quot;&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/cargo.jpg 1920w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/cargo.jpg 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/cargo.jpg 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/cargo.jpg 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/cargo.jpg 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/cargo.jpg 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/cargo.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/cargo.jpg 1920w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/cargo.jpg 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/cargo.jpg 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/cargo.jpg 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/cargo.jpg 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/cargo.jpg 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/cargo.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/cargo.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/cargo.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/cargo.jpg 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/cargo.jpg 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/cargo.jpg 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/cargo.jpg 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/cargo.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/cargo.jpg&quot; width=&quot;1920&quot; height=&quot;1280&quot; alt=&quot;&amp;#x27;Cargo&amp;#x27; in large block letters on an SBB Cargo train. Photo by myself, all rights reserved.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201920%201280%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAJCAIAAACNL4o%2BAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjklEQVQYlQGDAXz%2BAPX7%2FPz%2F%2F%2FLz88nKyGt0ixMYKBEPCitEei9Ql0JJWY2Xp5mowFFrnyFAhQD%2F%2F%2F%2FY29y3urOZpbY4QlggKDggLUZbdKRrg7JYa5p%2FiaKloKWcmKw3QHwAzNTRvMPNvMPLhJa4OkdcQk5rZHeoh4SVh3Nyg3Z%2Bg32RMz52tJujOVOQAJF%2FdpSivoKVupCWrSEycE9LbpyTolVee1NDXJqco21%2FnyVDg7Cksi1MjwC9hYdpf6qEmLimk6AcM4M8SXlkbJOImrSilKKLkKuYlZ6dm6SdiJohMnUAkWFvgJOtipiujoKVKjFxQ0JqWlF4hoaho4eQZWaRSElyXVp8VEFuOzluAHRTZz1TfEBPdUBJeS0qTR4ePj8%2BbYqXtpKGmy5KkRM1dwsxdi84eBgydgAvJSMeISgaICs4ND1OMitYOzddPUNiSlNyRExlPU5qSVhpR1pzQlJlO04ADw8LDQsFGBQNHRoTNCspTkVCJyklJSAYNjMtOjgzMzEsMismIBUPIxUQpQqd6mWmXCwAAAAASUVORK5CYII%3D%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;
    &lt;figcaption&gt;&#39;Cargo&#39; in large block letters on an SBB Cargo train. Photo by myself, all rights reserved.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/rathaus.jpg 1920w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/rathaus.jpg 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/rathaus.jpg 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/rathaus.jpg 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/rathaus.jpg 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/rathaus.jpg 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/rathaus.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/rathaus.jpg 1920w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/rathaus.jpg 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/rathaus.jpg 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/rathaus.jpg 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/rathaus.jpg 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/rathaus.jpg 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/rathaus.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/rathaus.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/rathaus.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/rathaus.jpg 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/rathaus.jpg 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/rathaus.jpg 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/rathaus.jpg 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/rathaus.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/rathaus.jpg&quot; width=&quot;1920&quot; height=&quot;1280&quot; alt=&quot;Rathaus ZVV tram stop. Photo by myself, all rights reserved.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201920%201280%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAJCAIAAACNL4o%2BAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjklEQVQYlQGDAXz%2BABgYFGpxdUBTaE9OSmhmYGdkXWtnYHBrY2ZiWmhlXmVjXXZ1cGdmYTIwLQBJRD2Fh4lCXHtSVlhRX28ySmc4UW08VXI4U3VOY3xkY11NTkpPUE04NjEANDQwiIeCWmt%2BaWxuWXiaRnivS3muOGymI12fRG%2Bec3BpXl9aYWBaOzcvACgpJn18eaWmqYKBfE5ihEBflE9snEVklx9ChTRQh4eEfXNxaXZzbC4qIgAxMS54d3SKiYd9e3NLTnZMT4ZUV41bXZJGSIdLTISDf3p%2BeXJ6dnAeGQ8ANDUycm5pe3dxent0rE1Wvz9LwDk9wzEzxxkVxS4oj4WAjYqDjoqEKCQcAC0rJTk4N3JuaGhqZ8R2YtV7ataDhdeFhNZzcc56eHx0b2hjXG9rZDQuHQAgGRonLDJoZV5zdHbQs2TavnXQ4fHO2dvR4OTM2t%2BFg350bmh%2Fe3VPRzoAIxwZNDU2dnVxamhjd3Npa2hfZGJgaGZhcW5rdnNweXZxb2tjc3FrQTs0%2F1yYwbPdH2QAAAAASUVORK5CYII%3D%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;
    &lt;figcaption&gt;Rathaus ZVV tram stop. Photo by myself, all rights reserved.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/poster1.jpg 1920w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/poster1.jpg 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/poster1.jpg 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/poster1.jpg 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/poster1.jpg 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/poster1.jpg 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/poster1.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/poster1.jpg 1920w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/poster1.jpg 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/poster1.jpg 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/poster1.jpg 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/poster1.jpg 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/poster1.jpg 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/poster1.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/poster1.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/poster1.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/poster1.jpg 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/poster1.jpg 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/poster1.jpg 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/poster1.jpg 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/poster1.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/poster1.jpg&quot; width=&quot;1920&quot; height=&quot;1280&quot; alt=&quot;Posters near Zurich University of the Arts&amp;#x27;s Toni campus. Photo by myself, all rights reserved.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201920%201280%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAJCAIAAACNL4o%2BAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZUlEQVQYlRXLTW%2FaMBgAYP%2FOHXfobdM09bLL1Emd6NTSHqCtBl2BIuhWKGqY81HAAWdOHDuxHUh4nal%2Fpur1kR7UbJ50ulf34%2F5g0Lkf9%2Fq9606n3Tg5%2FnVz7eInjLEfLOJE9AdDdPz928XF2Wh02%2B1etltn%2Fd7Pv3j%2B5MzzXEf%2FmOt60%2BmMUvbw8IiOjr62W83z89Px6O5xOgGw6zX1%2FEBmmhDiOM5qRTiXi8UKNX40MHaSJM2yjFKqtVkuyXJJcmVEmhpjtNFKF5wL9PHzJ8eZKV0IIXw%2FUErRzVuo65dyV5ZlqbURMosTjobD280mBKjzXAkpq2pvra3r%2F0ZrY4wpCqV0wgWNGLq8apGQANhdWVprq6oCsACwLUxV7bfbnTZGCBlFDL3%2F8MXBvoU3BdgDQFkBgNXahJsoTriQMhXS9Xz07uBwMpvHjK7XYfAccB4TQjzPY4ytSIhdL3hesDj9%2FWfyCuvAE%2Fj4xxtCAAAAAElFTkSuQmCC%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;
    &lt;figcaption&gt;Posters near Zurich University of the Arts&#39;s Toni campus. Photo by myself, all rights reserved.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/poster2.jpg 1920w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/poster2.jpg 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/poster2.jpg 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/poster2.jpg 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/poster2.jpg 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/poster2.jpg 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/poster2.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/poster2.jpg 1920w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/poster2.jpg 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/poster2.jpg 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/poster2.jpg 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/poster2.jpg 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/poster2.jpg 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/poster2.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/poster2.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/poster2.jpg 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/poster2.jpg 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/poster2.jpg 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/poster2.jpg 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/poster2.jpg 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/poster2.jpg 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/poster2.jpg&quot; width=&quot;1920&quot; height=&quot;1280&quot; alt=&quot;Another poster near Zurich University of the Arts&amp;#x27;s Toni campus. Photo by myself, all rights reserved.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201920%201280%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAJCAIAAACNL4o%2BAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjklEQVQYlQGDAXz%2BADAxMXFxcb6%2BvMDBtHd3aDAxNCgpMIODeJiZjnl7cT5APyEhIzU1OXt3fgCgoaGoqKfExcLKysC9vKSurZSVk2%2BlnnfSxovKxaiqra1pa3NnanV9hZMAu7u7tLOwtre3oKGipqWoubm119bIw8CvuLOgr6ujwsDCwsfOoay3s7TGAM3MzLKxrqysrY%2BPkouMjqanqsjIz5iZoY2OlpOVm7CvtczGypCZpZ%2BBpADEx8u5uLazs7PCwsW%2BvsC7u73IyMnFxMfCwcXEw8fBwsfCvb2Xlp1OYHgAxsvXvMTOu77En5%2Bht7a41NPV4%2BLk5OTm5eXp4N%2Fj3t3g1NXVp7G%2FZG5%2BALrAy9La5r7Ax5KRk7KytNzc3%2BPj5bq7vbm6vNbW2dvb3dLT06y4ylxqgABhY2jDydOxs7eMjI2BgoSjo6aWl5menqCkpKesrK%2FNztLS0dCqs7pYaH4AYmRnkJSZamxvTU1Ojo%2BSl5mbcHByr6%2Bx1tbYxsfKoaWo0sXDqJWURllu8Hz07n0kHZ8AAAAASUVORK5CYII%3D%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;
    &lt;figcaption&gt;Another poster near Zurich University of the Arts&#39;s Toni campus. Photo by myself, all rights reserved.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Inspired by all the instances of &lt;a href=&quot;https://en.wikipedia.org/wiki/Swiss_Style_(design)&quot;&gt;Swiss style&lt;/a&gt; design that I had seen, I set about redesigning my website in this style. The first step was finding a suitable neo-grotesque sans-serif font face. Helvetica was, no doubt, the best option. Monotype designed an update of Helvetica in 2019 called &lt;a href=&quot;https://www.monotype.com/fonts/helvetica-now&quot;&gt;Helvetica Now&lt;/a&gt;, and it looked to be a pretty great choice; it was also $548.99, so I looked to other potential alternatives. In my search, I discovered several other amazing fonts: &lt;a href=&quot;https://www.swisstypefaces.com/fonts/suisse/&quot;&gt;Suisse&lt;/a&gt;, &lt;a href=&quot;https://pangrampangram.com/products/neue-montreal&quot;&gt;Neue Montreal&lt;/a&gt;, and &lt;a href=&quot;https://klim.co.nz/collections/soehne/&quot;&gt;Söhne&lt;/a&gt;. They all looked like what I was looking for, but unfortunately I wasn’t prepared to allocate a particularly significant amount of capital to a font family for my personal website. So what you’re looking at now is what is possibly the most overused free and open source font family in the history of web design: &lt;a href=&quot;https://rsms.me/inter/&quot;&gt;Inter&lt;/a&gt;. The majority of glyphs in Inter actually look somewhat similar to Helvetica, and while experimenting with font features, I discovered the &lt;code&gt;ss07&lt;/code&gt; (square punctuation) and &lt;code&gt;ss08&lt;/code&gt; (square quotes) stylistic sets which, when enabled, made Inter look &lt;em&gt;a lot&lt;/em&gt; more like Helvetica and less like Inter.&lt;/p&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/helvetica.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/helvetica.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/helvetica.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/helvetica.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/helvetica.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/helvetica.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/helvetica.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/helvetica.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/helvetica.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/helvetica.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/helvetica.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/helvetica.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/helvetica.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/helvetica.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/helvetica.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/helvetica.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/helvetica.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/helvetica.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/helvetica.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/helvetica.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/helvetica.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/helvetica.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/helvetica.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/helvetica.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/helvetica.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;Comparison between Helvetica Neue, Inter Variable, and Inter Variable with ss07 and ss08 enabled.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAAA3ElEQVQYlW2Q0YqFIBBA7%2F%2F%2FW9zyIUtMnAZJksxCVzGwZYu9bLDnYWZeDgfmdZ5nKcU5t%2B97zvn8j1LK537dCxG7rhMXwzB0Xcc5Z4wBgBACEZ1zD8EYAwBaayklIkopxwulFCLmnD%2BRH6GUMo4j59wYY61d13VZlns657ZtM8Zs2%2FYozPMspQQAxljf95TSvu8ZY0IIzrn3PqX0KGitEREApmmKMX5dhBDSL8dxPApCiKqqmqap65oQ8n6%2FCSFN01BK27ZVSllrH0LOOcaYUvLehxD8RQjh7vx96zc8VGa3FgqhOgAAAABJRU5ErkJggg%3D%3D%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;
    &lt;figcaption&gt;Comparison between Helvetica Neue, Inter Variable, and Inter Variable with ss07 and ss08 enabled.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;p&gt;After deciding upon the font, the next typographic features to make modifications to were the font sizes and weights. I decided to try out increasing font sizes to dramatically large sizes, especially to emphasize one important feature on a page; I also increased the general font size scale across the website. The largest font size in use went up to &lt;code&gt;8rem&lt;/code&gt; (a maximum of &lt;code&gt;128px&lt;/code&gt; depending on your viewport size), making the visual center of a page more apparent and conveying important information in a more upfront manner upon initial viewing. The increase in differences between adjacent levels of font sizes in the visual hierarchy also meant that the overall layout and flow of pages became much clearer than before.&lt;/p&gt;
&lt;p&gt;In addition to typography, I also experimented with introducing more bits and pieces of color onto the website. One of the ways in which I did this was making the punctuation mark (e.g. periods, exclamation marks) at the end of prominently displayed titles or sentences on pages use the website’s global accent color. This adds some liveliness to the overall design by introducing some variation, as well as drawing more attention to the parts of a page that require a viewer’s initial attention. Borrowing a neat trick from &lt;a href=&quot;https://miniflux.app/&quot;&gt;Miniflux&lt;/a&gt;, I also made part of my name (i.e. the website’s title) have color and switch to the other part having color on hover.&lt;/p&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/two.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/two.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/two.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/two.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/two.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/two.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/two.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/two.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/two.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/two.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/two.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/two.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/two.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/two.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/two.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/two.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/two.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/two.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/two.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/two.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/two.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/two.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/two.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/two.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/two.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;A stylized screenshot of part of my website at the time of writing.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAAAhElEQVQYlZXQMQrDMAwF0NwfPBUKpc05vHnwDTKZ2qKRjBfjLYOhBlchpBQyVKV%2F1uOLPzDz6xCWMzBz77219vv2A4wxWmvC2d0fMT%2F3ThaAtVYpdbteTudxCsvWKYNaKyICQIwRcXbOlVIkkHMOIaSUiAgAvPdE9O2rDfyVNzguK627Amk2cgnFrM17AAAAAElFTkSuQmCC%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;
    &lt;figcaption&gt;A stylized screenshot of part of my website at the time of writing.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;p&gt;How long will this iteration of my website stay in use? I’m not sure myself. It might be here for a month, a quarter, or even a year. Nonetheless, I love this design, and not just because it is a realization of an aesthetic ideal that I’ve admired for a long time; it’s also because it is the cumulative result of the long way I’ve come in software, and especially web, development.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I still remember my very first attempt at a personal website a few years ago, when I first started: a crude clone of &lt;a href=&quot;https://web.archive.org/web/20210117182720/https://evanyou.me/&quot;&gt;Evan You’s website at the time&lt;/a&gt;, uploaded as a ZIP archive and hosted on a random free website host.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/evanyou.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/evanyou.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/evanyou.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/evanyou.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/evanyou.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/evanyou.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/evanyou.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/evanyou.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/evanyou.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/evanyou.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/evanyou.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/evanyou.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/evanyou.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/evanyou.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/evanyou.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/evanyou.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/evanyou.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/evanyou.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/evanyou.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/evanyou.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/evanyou.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/evanyou.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/evanyou.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/evanyou.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/evanyou.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;A screenshot of Evan You&amp;#x27;s website&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAAApElEQVQYlYWQSw6DMAxEe%2F%2BLcYaiFhJEUcjHThxoMFKbimy6gNK3mJWfZuRLLrwK70I%2B5ZJzBsSqqur6en76FaZp6jo5DMM4au99%2Fius6xpC8OhdgYgoxkiReTkW5nnupGylaGTb970Qohb3m2wO2zaBmQFAO2MBmDkVnikx889JgBhCQEAAGI1%2BKOWJeOH9yzYhpaSUstYaY7e01jmntSaifcMHVcVzQnkeGOMAAAAASUVORK5CYII%3D%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;As the content of this website diversified, with blog posts and other content pages, its design changed rapidly as well. The next major iteration that I can distinctly recall is one built with Next.js, with a design referencing that of &lt;a href=&quot;https://web.archive.org/web/20210613124842/https://brianlovin.com/&quot;&gt;Brian Lovin’s old website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/brianlovin.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/brianlovin.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/brianlovin.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/brianlovin.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/brianlovin.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/brianlovin.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/brianlovin.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/brianlovin.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/brianlovin.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/brianlovin.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/brianlovin.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/brianlovin.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/brianlovin.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/brianlovin.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/brianlovin.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/brianlovin.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/brianlovin.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/brianlovin.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/brianlovin.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/brianlovin.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/brianlovin.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/brianlovin.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/brianlovin.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/brianlovin.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/brianlovin.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;A screenshot of Brian Lovin&amp;#x27;s website&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAAAtElEQVQYlW2Oiw7CMAhF%2Ff9v1ETjY85YWgdlpYXW1Mdi4k4IIYF7L5v6JcaIiKqac9YXIlJrFRFVXc429qXW2lp7D53WetXelxsz%2Bwi6U0qRJmaOkQmnx%2F2I%2FkLhNsGgWv4FjSPdxjMAjOP1ctwOp931vB%2BHAz5gPSElAe9DCAAQPIAPzoFzgESl%2FCWYWSmFiBAxJUEkZjZTLfq2WxFY33%2FIOf%2B6rr%2FEzO7FPM8ikn7IOS%2BCJwIzdVGyAtQhAAAAAElFTkSuQmCC%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;These early days of this website were often imitations of other peoples’ websites whose design I really enjoyed when I happened to come across them on the Internet. I didn’t know much about how to design interfaces by myself yet, and even if I had a rough idea of what something should look like, I didn’t have the ability to transform that idea into a reality that matched my vision.&lt;/p&gt;
&lt;p&gt;The modern story of this website, and the codebase currently in use today, starts in 2021. Having come across &lt;a href=&quot;https://web.archive.org/web/20210601134504/https://macwright.com/&quot;&gt;Tom MacWright’s website&lt;/a&gt;, I was fascinated with the minimal and clean design language that it used: a primarily black and white color palette, a mostly uniform font size throughout the website, and singular Unicode arrow symbols indicating navigation state.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/macwright.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/macwright.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/macwright.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/macwright.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/macwright.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/macwright.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/macwright.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/macwright.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/macwright.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/macwright.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/macwright.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/macwright.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/macwright.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/macwright.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/macwright.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/macwright.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/macwright.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/macwright.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/macwright.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/macwright.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/macwright.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/macwright.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/macwright.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/macwright.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/macwright.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;A screenshot of Tom MacWright&amp;#x27;s website&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAAAqUlEQVQYlXWP0Y6EMAhF5%2F%2B%2F0Bf1RZPG2FSoQC0L3ayTMU6ynscLJxderbVSCgCUUnLOzNxuuLuImNmVvFpr8YSIuq6bpsndr7GZhRCO4%2FgSmLnWKiIhBESstd4bUkr35E94s%2B%2F7OI4xRkQkopyzqrr7uq6PwjzPACAieuInKSVVfRQQEQCuDTOLMf7fQER93w%2FDgIg%2FH1T18SQiWpYlf3i%2Fwczbtt1P%2BgWNaHS4CGxMFAAAAABJRU5ErkJggg%3D%3D%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The choice of a two-column layout is, I believe, relatively uncommon for personal websites, but I liked the look, so I went about cloning this design on my website with &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Many things have changed in the years since then. I went through many, some might even argue too many, font choices and pairings (&lt;a href=&quot;https://fontshare.com/fonts/switzer&quot;&gt;Switzer&lt;/a&gt;, &lt;a href=&quot;https://www.fontshare.com/fonts/satoshi&quot;&gt;Satoshi&lt;/a&gt;, &lt;a href=&quot;https://rsms.me/inter/&quot;&gt;Inter&lt;/a&gt;, &lt;a href=&quot;https://github.com/IBM/plex&quot;&gt;IBM Plex Sans&lt;/a&gt;, and &lt;a href=&quot;https://github.com/playbeing/dinish&quot;&gt;DINish&lt;/a&gt;, to name a few); I added more than a dozen themes and removed all of them except for light and dark after a while; I shifted parts of the website around to experiment with where everything fits together; and I adapted the design to the increasing amount of information on the website while adding more features (tags, read counts, additional pages, etc.)&lt;/p&gt;
&lt;p&gt;Throughout these changes, the two-column layout mostly remained, and as my experience with design and web development in general increased, I became more comfortable with making changes that reflected what I personally thought was most aesthetically pleasing. Hours were spent on tweaking font weights, gaps, transition properties, and letter spacings, and with every change, a different style emerged.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/cdn-cgi/image/f=avif,w=2400/images/five-years-of-design/dinish.png 2400w, /cdn-cgi/image/f=avif,w=2048/images/five-years-of-design/dinish.png 2048w, /cdn-cgi/image/f=avif,w=1920/images/five-years-of-design/dinish.png 1920w, /cdn-cgi/image/f=avif,w=1200/images/five-years-of-design/dinish.png 1200w, /cdn-cgi/image/f=avif,w=1080/images/five-years-of-design/dinish.png 1080w, /cdn-cgi/image/f=avif,w=828/images/five-years-of-design/dinish.png 828w, /cdn-cgi/image/f=avif,w=750/images/five-years-of-design/dinish.png 750w, /cdn-cgi/image/f=avif,w=640/images/five-years-of-design/dinish.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/cdn-cgi/image/f=webp,w=2400/images/five-years-of-design/dinish.png 2400w, /cdn-cgi/image/f=webp,w=2048/images/five-years-of-design/dinish.png 2048w, /cdn-cgi/image/f=webp,w=1920/images/five-years-of-design/dinish.png 1920w, /cdn-cgi/image/f=webp,w=1200/images/five-years-of-design/dinish.png 1200w, /cdn-cgi/image/f=webp,w=1080/images/five-years-of-design/dinish.png 1080w, /cdn-cgi/image/f=webp,w=828/images/five-years-of-design/dinish.png 828w, /cdn-cgi/image/f=webp,w=750/images/five-years-of-design/dinish.png 750w, /cdn-cgi/image/f=webp,w=640/images/five-years-of-design/dinish.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;/cdn-cgi/image/f=jpeg,w=2400/images/five-years-of-design/dinish.png 2400w, /cdn-cgi/image/f=jpeg,w=2048/images/five-years-of-design/dinish.png 2048w, /cdn-cgi/image/f=jpeg,w=1920/images/five-years-of-design/dinish.png 1920w, /cdn-cgi/image/f=jpeg,w=1200/images/five-years-of-design/dinish.png 1200w, /cdn-cgi/image/f=jpeg,w=1080/images/five-years-of-design/dinish.png 1080w, /cdn-cgi/image/f=jpeg,w=828/images/five-years-of-design/dinish.png 828w, /cdn-cgi/image/f=jpeg,w=750/images/five-years-of-design/dinish.png 750w, /cdn-cgi/image/f=jpeg,w=640/images/five-years-of-design/dinish.png 640w&quot; sizes=&quot;100vw&quot;&gt;&lt;img src=&quot;/cdn-cgi/image/f=auto/images/five-years-of-design/dinish.png&quot; width=&quot;2400&quot; height=&quot;1200&quot; alt=&quot;The DINish font specimen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;100vw&quot; style=&quot;content-visibility: auto; background-size: cover; background-image: url(&amp;#x22;data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202400%201200%22%3E%3Cfilter%20id%3D%22a%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeGaussianBlur%20stdDeviation%3D%2215%22%2F%3E%3CfeComposite%20in2%3D%22SourceGraphic%22%20operator%3D%22in%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncA%20tableValues%3D%221%201%22%20type%3D%22discrete%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Cimage%20width%3D%22100%25%22%20height%3D%22100%25%22%20filter%3D%22url(%23a)%22%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB%2FFOjAAAAACXBIWXMAAAsSAAALEgHS3X78AAAA4UlEQVQYlYWQS4qFQAxF3br70EGBuAedOHADiuBMQSlbdOIPK%2FUxyaO7mkfT0PQZZBC4J%2BEGy7Ls%2B05Ez98AgDHmeR5EDO77VkoxMyISkZ8eRGTm4zjCMEzTlJmJKOD%2FMMZUVdU0Tdd1930HAHCeZ9M0bdsSkZTSGPMr0Pe9lHIcRwAIhmGIokgIked5HMdlWU7T5K%2F7gLV2WRYA0Fp%2FvlTXdZIkQogsy9I0LYpinuefAefcdV34BTMH1tpt25RS67p6mdYaEd8VOed8E98teY33ece7Jb8EgGn6OI7TGMvML9i0aCIwdsNmAAAAAElFTkSuQmCC%22%20preserveAspectRatio%3D%22none%22%2F%3E%3C%2Fsvg%3E&amp;#x22;)&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;DINish was used for a while as the primary font; as a grotesque sans-serif typeface designed to be similar to &lt;a href=&quot;https://en.wikipedia.org/wiki/FF_DIN&quot;&gt;FF DIN&lt;/a&gt;, it made the website feel modern, condensed, industrial, and fast-paced. Before that, I used IBM Plex Sans, another grotesque sans-serif font that is also quite well designed and introduces more character to the website’s design (compared to, say, system font stacks). Despite the various font family changes, the typographic design of this website had not changed much since 2021.&lt;/p&gt;
&lt;p&gt;Which is why this recent redesign is such a milestone for this website and myself. Although I’m not a graphic designer myself by any measure, I’ve always been enamored with simple and elegant graphic design, whether it’s industrial design, typographic design, or human interface design, and I am happy to have been able to design something that, at least to my subjective point of view, embodies the functional and utilitarian aesthetic that I have hoped to attain, after many years of experience and practice.&lt;/p&gt;
</description>
      <pubDate>Thu, 25 Sep 2025 00:00:00 +0000</pubDate>
      <dc:creator>Ryan Cao</dc:creator>
      <guid>https://ryanccn.dev/posts/five-years-of-design/</guid>
    </item>
    <item>
      <title>A Corepack by Any Other Name</title>
      <link>https://ryanccn.dev/posts/corepack/</link>
      <description>&lt;p&gt;How many HTTP requests do you think &lt;a href=&quot;https://github.com/nodejs/corepack&quot;&gt;Corepack&lt;/a&gt; makes when you run &lt;code&gt;corepack use pnpm&lt;/code&gt; to use the latest version of pnpm in your project?&lt;/p&gt;
&lt;p&gt;Wrong — it’s both one and two. Two, in the sense that it makes two requests; one, because these two requests are &lt;em&gt;completely identical&lt;/em&gt;. I came across this little idiosyncrasy while working on &lt;a href=&quot;https://github.com/ryanccn/moldau&quot;&gt;Moldau&lt;/a&gt;, a version manager for Node.js package managers that is part of my increasingly expansive repertoire of cryptically named developer-oriented tools. For those of you familiar with Node.js, you probably use, or at least know of, the tool that is traditionally employed for this use case: Corepack. In a nutshell, Corepack allows you to use specific versions of package managers for different projects, with the version being specified in the &lt;code&gt;packageManager&lt;/code&gt; or &lt;code&gt;devEngines.packageManager&lt;/code&gt; field in the &lt;code&gt;package.json&lt;/code&gt; of each project; kind of like &lt;a href=&quot;https://nixos.wiki/wiki/Development_environment_with_nix-shell&quot;&gt;Nix&lt;/a&gt; development environments, or &lt;a href=&quot;https://mise.jdx.dev/&quot;&gt;mise&lt;/a&gt;, but exclusively for Node.js package managers.&lt;/p&gt;
&lt;p&gt;Since Corepack will, sadly, &lt;a href=&quot;https://github.com/nodejs/TSC/pull/1697#issuecomment-2737093616&quot;&gt;not be distributed in Node.js from Node.js v25 onwards&lt;/a&gt;, I thought it might be a fun idea to write a Corepack alternative myself in Rust. I embarked on this journey with bravado, aiming for best-effort compatibility with most of Corepack. But soon enough, I began to realize the great folly with which I had set myself on this task. As I ran into numerous weird compatibility issues and differences in Moldau’s behavior from Corepack, I realized that Corepack’s technical design was much more complicated than I thought.&lt;/p&gt;
&lt;p&gt;One of the most interesting choices that Corepack makes is that it uses a &lt;a href=&quot;https://github.com/nodejs/corepack/blob/aefde28a631356bfdec91795d2c60be07dbf5be3/config.json&quot;&gt;hardcoded config file&lt;/a&gt; for storing a &lt;em&gt;lot&lt;/em&gt; of package metadata. In Node.js, the &lt;code&gt;&quot;bin&quot;&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt; specifies which files in the package are to be installed as executables for the user to run, and if you download the tarball that the npm registry provides, you can retrieve this data from the &lt;code&gt;package.json&lt;/code&gt; in the tarball. With Corepack, though, this data is hardcoded in the config itself. This would an odd choice if Corepack downloads package managers as package tarballs, but as it happens, it does not!&lt;/p&gt;
&lt;p&gt;For Yarn, and Yarn only, Corepack downloads a singular JavaScript bundle from &lt;code&gt;repo.yarnpkg.com&lt;/code&gt; instead of downloading a tarball from &lt;code&gt;registry.npmjs.org&lt;/code&gt; if the major version is greater than or equal to 2. This fascinating behavior is necessitated by the fact that Yarn has a prodigiously confusing versioning scheme. Yarn “classic”, with a major version of less than or equal to 1, is distributed on the npm registry as &lt;code&gt;registry.npmjs.org/yarn&lt;/code&gt;; Yarn “modern”, or “berry”, with a major version of greater than or equal to 2, is distributed on &lt;code&gt;repo.yarnpkg.com&lt;/code&gt;. In the &lt;a href=&quot;https://dev.to/arcanis/introducing-yarn-2-4eh1#what-will-happen-to-the-legacy-codebase&quot;&gt;Yarn 2 release post&lt;/a&gt;, Maël Nison, the lead maintainer for Yarn, explains thus:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;yarn&lt;/code&gt; package on npm will not change; we will distribute further version [sic] using the new &lt;a href=&quot;https://yarnpkg.com/cli/set/version&quot;&gt;&lt;code&gt;yarn set version&lt;/code&gt;&lt;/a&gt; command.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yarn’s original release model relied on keeping the classic version of Yarn (i.e. &lt;code&gt;registry.npmjs.org/yarn&lt;/code&gt;) around for all users to install (in the interest of backwards compatibility) while having classic Yarn fetch bundles of modern Yarn to execute from &lt;code&gt;repo.yarnpkg.com&lt;/code&gt; if a user requested them. If you wanted to specify a version of Yarn to use in your project, you would set the &lt;a href=&quot;https://yarnpkg.com/configuration/yarnrc#yarnPath&quot;&gt;&lt;code&gt;yarnPath&lt;/code&gt;&lt;/a&gt; config to the path to the downloaded bundle (which could be checked into your project’s VCS as well).&lt;/p&gt;
&lt;p&gt;On March 12, 2020, &lt;a href=&quot;https://github.com/nodejs/corepack/commit/c12bf5e1ceeca85f84855a96dfa81530ba806296&quot;&gt;Corepack was born&lt;/a&gt;, written by none other than Maël Nison. Corepack was an elegant solution to the problem of package manager version management; by including the versions in &lt;code&gt;package.json&lt;/code&gt;s and distributing the tool to read these versions alongside Node.js, everyone could easily get on the same page. Since the release of Yarn v3, Yarn’s official documentation has &lt;a href=&quot;https://yarnpkg.com/corepack&quot;&gt;recommended using Corepack&lt;/a&gt; rather than installing Yarn globally or using &lt;code&gt;yarn set version&lt;/code&gt;; in fact, in Yarn v4, &lt;code&gt;yarn set version&lt;/code&gt; defaults to configuring Corepack instead of modifying the &lt;code&gt;yarnPath&lt;/code&gt; config property. But an issue remained: Yarn v2 was not distributed anywhere other than &lt;code&gt;repo.yarnpkg.com&lt;/code&gt; (as far as I’m aware); consequently, in order to support Yarn v2, Corepack still had to download the bundles from &lt;code&gt;repo.yarnpkg.com&lt;/code&gt;, as &lt;code&gt;yarn set version&lt;/code&gt; did, if users tried to use modern Yarn. This also made it necessary for Corepack to hardcode the names of executables in its config, since the information was not readily available for the downloaded bundles of Yarn (pun most definitely intended).&lt;/p&gt;
&lt;p&gt;Thus, in order to support both classic and modern Yarn, Corepack has two configs for Yarn, differentiated by the SemVer range &lt;code&gt;&amp;#x3C;2.0.0&lt;/code&gt; and &lt;code&gt;&gt;=2.0.0&lt;/code&gt;. This config duplication also extended to pnpm, when in &lt;a href=&quot;https://github.com/pnpm/pnpm/blob/1f65bdd1c220386e6cefbacca317782171629971/pnpm/CHANGELOG.md#600&quot;&gt;v6.0.0&lt;/a&gt; they renamed their executable files from &lt;code&gt;bin/{pnpm,pnpx}.js&lt;/code&gt; to &lt;code&gt;bin/{pnpm,pnpx}.cjs&lt;/code&gt;. Since the executable names were hardcoded, Corepack had to create a new config for &lt;code&gt;&gt;=6.0.0&lt;/code&gt; with an updated, hardcoded &lt;code&gt;&quot;bin&quot;&lt;/code&gt;. And this is where it gets exceedingly interesting; take a look at the following code snippet.&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;// https://github.com/nodejs/corepack/blob/aefde28a631356bfdec91795d2c60be07dbf5be3/sources/Engine.ts#L409-L415

const versions = await Promise.all(
  Object.keys(definition.ranges).map(async (range) =&gt; {
    const packageManagerSpec = definition.ranges[range];
    const registry = corepackUtils.getRegistryFromPackageManagerSpec(packageManagerSpec);

    const versions = await corepackUtils.fetchAvailableVersions(registry);
    return versions.filter((version) =&gt;
      semverUtils.satisfiesWithPrereleases(version, finalDescriptor.range),
    );
  }),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Corepack doesn’t choose one of the duplicated configs to fetch from; it fetches all of them and joins the matching versions together! The technical choices that Corepack made it so that it had to obtain information from all the ranges before it could resolve the final version if the user provides a non-exact version, and because the two configs for pnpm differ in nothing other than the &lt;code&gt;&quot;bin&quot;&lt;/code&gt; field, Corepack makes two completely identical requests for the same package data if you ask it for a version of pnpm.&lt;/p&gt;
&lt;p&gt;For Moldau, I did not want to implement a whole set of code just for downloading Yarn while I could handle npm and pnpm easily through a generic interface to the npm registry. Fortunately, I discovered that versions of Yarn from v3 upwards were also published to an npm package with the name of &lt;code&gt;@yarnpkg/cli-dist&lt;/code&gt;; &lt;a href=&quot;https://github.com/heroku/buildpacks-nodejs/blob/6802aa12bbd3029058fb0c462097cd889373224e/common/bin/download-verify-npm-package#L23-L27&quot;&gt;Heroku’s buildpacks&lt;/a&gt; and &lt;a href=&quot;https://github.com/volta-cli/volta/blob/a7384fa4fc7a0eca961032da4d962d94218b5868/crates/volta-core/src/tool/yarn/resolve.rs#L82-L91&quot;&gt;Volta&lt;/a&gt; use it for installing Yarn, and Corepack even uses it as a fallback for when the user specifies a custom npm registry URL. All in all, this meant that Yarn was distributed in &lt;em&gt;three&lt;/em&gt; locations as far as I knew:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;registry.npmjs.org/yarn&lt;/code&gt;: v1.*, v2.0.0-rc.24, v2.0.0-rc.27, and v2.4.3&lt;/li&gt;
&lt;li&gt;&lt;code&gt;registry.npmjs.org/@yarnpkg/cli-dist&lt;/code&gt;: v3.*, v4.*, v2.4.1, and v2.4.2&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repo.yarnpkg.com&lt;/code&gt;: v0.*, v1.*, v2.*, v3.*, and v4.*&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the interest of the DRY principles and the soundness of my mental faculties, I decided to use the &lt;code&gt;@yarnpkg/cli-dist&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt; packages on the npm registry to install Yarn versions in Moldau; this way, all three supported package managers could share the same code that interacts with &lt;code&gt;registry.npmjs.org&lt;/code&gt;. Of course, this came at the cost of not being able to support most Yarn v2 versions; however, usage of Yarn v2 with Corepack is low (as far as I could tell from a cursory GitHub code search), since Corepack only became the preferred installation method with Yarn v3, and it is unlikely that users will choose to use Yarn v2 today.&lt;/p&gt;
&lt;p&gt;After implementing the functionality to download a package tarball and unpack it, I ran into yet another issue that was caused by Corepack’s eccentric handling of Yarn. The &lt;code&gt;packageManager&lt;/code&gt; key in &lt;code&gt;package.json&lt;/code&gt; optionally supports specifying a hash for verifying the integrity of the downloaded package manager; however, with Yarn, the hash that Corepack uses is the hash of the &lt;em&gt;bundle&lt;/em&gt;, not the package tarball, since it doesn’t download the tarball in the first place. To resolve this issue, I had to write some Yarn-specific code: when verifying integrity hashes for Yarn, Moldau finds the Yarn executable bundle in the unpacked package and calculates the hash of that bundle, instead of calculating the hash of the tarball as it does with npm and pnpm. The things we do for compatibility.&lt;/p&gt;
&lt;p&gt;In general, I am quite satisfied with what I achieved with Moldau. Despite the various hiccups and pitfalls in making Moldau compatible with Corepack’s core functionality, I made several of what are, in my opinion, improvements over Corepack’s design. In order to figure out whether to use &lt;code&gt;@yarnpkg/cli-dist&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; when resolving Yarn versions, I implemented a heuristic that determines whether modern Yarn or classic Yarn is being requested based on the SemVer version or range provided by the user, so that Moldau only makes requests that are necessary:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;let is_classic = self.version.exact().is_some_and(|v| v.major &amp;#x3C;= 1)
	|| self.version.semver_req().is_some_and(|r| {
		r.comparators.iter().any(|c| match c.op {
			semver::Op::Exact
			| semver::Op::LessEq
			| semver::Op::Tilde
			| semver::Op::Caret =&gt; c.major &amp;#x3C;= 1,
			semver::Op::Less =&gt; {
				c.major &amp;#x3C;= 1
					|| c.major == 2
						&amp;#x26;&amp;#x26; c.minor.is_none_or(|n| n == 0)
						&amp;#x26;&amp;#x26; c.patch.is_none_or(|n| n == 0)
			}
			_ =&gt; false,
		})
	});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I did not have to hardcode information on where package managers’ executables are in Moldau, since this information could be dynamically read from the &lt;code&gt;&quot;bin&quot;&lt;/code&gt; key from the unpacked packages’ &lt;code&gt;package.json&lt;/code&gt;s. I added a nice progress bar when downloading package tarballs, powered by the &lt;a href=&quot;https://docs.rs/indicatif&quot;&gt;indicatif&lt;/a&gt; crate, and I left out several pieces of functionality that I personally didn’t deem to be useful, such as the Known Good Releases mechanism and auto pin. And, as goes without saying, Moldau is marginally faster than Corepack due to being written in Rust and compiled to native code.&lt;/p&gt;
&lt;p&gt;Corepack is a very impressive project, and it’s certainly been an experience going through source code, docs, and blog posts to understand how it works and why it is implemented the way it is. &lt;a href=&quot;https://github.com/ryanccn/moldau&quot;&gt;Moldau&lt;/a&gt; was born out of these endeavors as something of a Corepack by another name, but I believe it to be much more than that; if you happen to try it out (&lt;code&gt;cargo binstall moldau&lt;/code&gt;), I hope you will find that it smells as sweet.&lt;/p&gt;
</description>
      <pubDate>Sat, 26 Apr 2025 00:00:00 +0000</pubDate>
      <dc:creator>Ryan Cao</dc:creator>
      <guid>https://ryanccn.dev/posts/corepack/</guid>
    </item>
    <item>
      <title>Ephemeral Permissions Considered Beneficial</title>
      <link>https://ryanccn.dev/posts/ephemeral-permissions/</link>
      <description>&lt;p&gt;In last year’s macOS Sequoia release, one change attracted a &lt;a href=&quot;https://9to5mac.com/2024/08/06/macos-sequoia-screen-recording-privacy-prompt/&quot;&gt;lot&lt;/a&gt; &lt;a href=&quot;https://www.macrumors.com/2024/08/15/macos-sequoia-screen-recording-app-permissions/&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://appleinsider.com/articles/24/08/07/users-have-to-confirm-screen-recording-permission-every-week-in-macos-sequoia&quot;&gt;attention&lt;/a&gt;: the new periodic confirmation for apps that use screen recording permissions. Every month, macOS would display a prompt to confirm a user’s intent to continue allowing an app access to their screen when the app is used. This change drew criticism from a lot of macOS users, who accused the feature of being intrusive, unnecessary, or anticompetitive; projects such as &lt;a href=&quot;https://goodsnooze.gumroad.com/l/amnesia&quot;&gt;Amnesia&lt;/a&gt; were even created to solve the singular problem of these permission prompts.&lt;/p&gt;
&lt;p&gt;Ephemeral permissions models like these appear to be annoying at first sight. They put more obstacles in users’ ways when they are trying to get their computers to do something, adding yet another frustrating dialog to click through in order to reach their ultimate objective. However, the privacy benefits that ephemeral permissions bring &lt;strong&gt;far outweigh&lt;/strong&gt; the increase in required user interactions that they are associated with. The operating system’s (or browser’s) permissions model is the vital boundary that guards the increasingly powerful capabilities of users’ devices against potential intrusion or misuse, and ephemeral permissions play an important role in protecting users from potentially malicious applications and &lt;em&gt;themselves&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In current systems, most permissions are typically designed to be granted permanently. Once a user presses “Allow”, that permission is granted for the application until the user, for whatever reason, decides to go into settings and revoke that permission. This assumption, that an application should always have access to a specific capability when the user has only granted it once, is a convenient yet dangerous one, especially for more sensitive capabilities such as geolocation, microphone, camera, and screen recording. Permissions systems exist because applications installed by the user are not considered fully trusted; they should only have access to whatever the user &lt;em&gt;currently&lt;/em&gt; needs them to access in order to function. For the same reason that applications do not have access to all of a device’s capabilities by default, they should not have access to a capability forever based on a one-time expression of intent. For example, if a user grants Discord access to their microphone for a voice chat on a certain occasion, their ultimate intent is not to allow the app to access their microphone anytime, anywhere, but to allow the app to access their microphone &lt;em&gt;for the duration of the voice chat&lt;/em&gt;. Would it not make more sense, then, to have the microphone permission only be granted to the app for a limited timeframe and then revoked, for instance, when the user closes the app, or when the device is locked, or after a week?&lt;/p&gt;
&lt;p&gt;Permissions systems sometimes address this problem by providing indicators of access to permissions, especially sensitive ones. For instance, macOS displays &lt;a href=&quot;https://support.apple.com/guide/mac-help/quickly-change-settings-mchl50f94f8f/mac&quot;&gt;privacy indicators&lt;/a&gt; in Control Center and in the menu bar when sensitive permissions such as the microphone, camera, location, and system audio are being used by applications (incidentally, a subset of users also consider this to be unnecessary). The &lt;a href=&quot;https://support.apple.com/en-us/102188&quot;&gt;App Privacy Report&lt;/a&gt; on iOS and iPadOS allows users to see a record of when and how apps access permissions (data &amp;#x26; sensors) granted them. These features give users insights into when the permissions that they had granted in the past are being used, and help to surface unauthorized access patterns. However, it is oftentimes far too easy to ignore the signals provided by these privacy features; after all, they &lt;strong&gt;do not require user interaction&lt;/strong&gt;! A small yellow dot showing up in the top right corner of a display is quite easy to ignore, especially if the user is engrossed in a task that demands their full attention elsewhere on the screen.&lt;/p&gt;
&lt;p&gt;In addition, the number of apps and websites that an individual uses and the number of permissions and capabilities that devices have are both steadily rising; it is impossible to assume that the average user will be able to keep track of everything they have authorized the applications that they use to access. An iOS user could very well forget that they had given an app access to their calendar, until some day by a fluke they happen to check their App Privacy Report and realize that it has been periodically accessing their calendar data for no apparent reason. Opaque business practices and legalese-filled privacy policies further make it difficult to hold applications accountable for where data goes after it is collected from devices’ capabilities. Thus, it is the increasingly indispensable duty of the operating system/browser, acting as the security boundary, to actively remind the user of these permissions (by making them temporally limited) and give them an opportunity to periodically review them.&lt;/p&gt;
&lt;p&gt;Ephemeral permissions ensure that users will have to review their privacy settings on a regular basis through active interactions with the permissions system. Whether implemented through centralized settings or operating system/browser prompts, the fact that an application has authorization to access a specific capability on a device is prominently brought to the user’s attention. The user might want the application to continue having the permission; in that case, a few interactions will confirm such an intent. If the user does not want the application to continue having the permission, however, this will bring the issue to their attention and they will decline to grant the permission or deny it for the application. In any case, the ephemeral nature of these permissions will require active user intent to periodically confirm or deny them, thereby ensuring that the user’s preferences (which might often change) are always reflected in their privacy settings.&lt;/p&gt;
&lt;p&gt;Ephemeral permissions systems also automatically revoke permissions or remind users to revoke them when they are no longer needed. In the previously mentioned Discord example, a good ephemeral permissions model will either (a) remove the microphone permission from the app at some point in time after the voice chat or (b) remind the user that the app has access to their microphone, at which point they can choose to grant or deny the permission. This behavior enforces the &lt;a href=&quot;https://en.wikipedia.org/wiki/Principle_of_least_privilege&quot;&gt;principle of least privilege&lt;/a&gt; for applications; they only have access to the permissions that are required for the function that the user uses them for. If a user gives camera access to a maps app for AR navigation one time but then does not use AR navigation ever again, ephemeral permissions systems will, at some point, revoke camera permissions for the maps app, ensuring that the maps app no longer has access to a permission which it does not need to function. Chromium’s “Automatically remove permissions from unused sites” setting is founded on such a premise; something that a user has not used for a while and perhaps does not even recall using should not have access to sensitive user data. Ephemeral permissions systems give users opportunities to reconsider whether an application currently needs the permission that it is requesting, reminding them of a choice that they can take but may have forgotten about or preemptively protecting them from unauthorized access.&lt;/p&gt;
&lt;p&gt;An interactive ephemeral permissions system design, in which permissions are granted and revoked as a user interacts with the application that is requesting the permission, helps to reduce the maintenance burden of privacy settings as well. Instead of having to go into a centralized privacy settings page and toggling permissions for prodigious lists of apps, users will be able to manage these permissions in the course of utilizing the functionalities that require them; the conscious effort that is required for a user to review their privacy settings is significantly reduced. Even for users that, for one reason or another, are regrettably predisposed to impetuously granting permissions to applications without considering their specific use cases or implications, ephemeral permissions systems might provide a significant improvement in privacy protection. Since permissions requests are repeatedly presented to the user over time, there is a greater chance of them actually reviewing the permissions request and making an active decision on it at some point; such an opportunity is not offered to the user in a permanent permissions system.&lt;/p&gt;
&lt;p&gt;Ephemeral permissions models provide a level of privacy guarantees and protection that significantly surpass “traditional” permanent permissions models. Any implementations of such systems have to strike a delicate balance between usability and privacy; they must keep the user actively aware of their permissions choices through requiring interaction, while avoiding obstructing the user’s daily usage to the point where they devise strategies to circumvent the very system designed to protect them. In general, ephemeral permissions are definitely a net positive for privacy. macOS Sequoia’s implementation of screen recording permissions was a commendable move in favor of this model, and it would be wonderful to see more ephemeral permissions systems in production across operating systems and browsers.&lt;/p&gt;
</description>
      <pubDate>Sat, 29 Mar 2025 00:00:00 +0000</pubDate>
      <dc:creator>Ryan Cao</dc:creator>
      <guid>https://ryanccn.dev/posts/ephemeral-permissions/</guid>
    </item>
    <item>
      <title>Where Did My Colorful Home Manager Logs Go? Debugging Ghostty and Sudo</title>
      <link>https://ryanccn.dev/posts/ghostty-sudo-terminfo/</link>
      <description>&lt;p&gt;Ever since switching to &lt;a href=&quot;https://mitchellh.com/ghostty&quot;&gt;Ghostty&lt;/a&gt;, my Home Manager activation has stopped printing its headers as cyan and bold. I never bothered enough to fix it, since I work on my flake most of the time in VS Code, but since I happened to be working on &lt;a href=&quot;https://github.com/ryanccn/morlana&quot;&gt;morlana&lt;/a&gt;, an alternative implementation for &lt;code&gt;darwin-{rebuild,installer,uninstaller}&lt;/code&gt;, and was getting acquainted with some of the internals of how a nix-darwin system is activated, I decided to take a look at what &lt;em&gt;exactly&lt;/em&gt; was causing the missing colors.&lt;/p&gt;
&lt;p&gt;My initial hypothesis, the one that I had from my first encounter with this problem, was that Home Manager had some sort of registry of &lt;code&gt;TERM&lt;/code&gt; variables to check color support, and this registry included &lt;code&gt;xterm-256color&lt;/code&gt; but not &lt;code&gt;xterm-ghostty&lt;/code&gt;. I dived into the Home Manager codebase in search of evidence for this hypothesis, but searches for &lt;code&gt;TERM&lt;/code&gt; and &lt;code&gt;xterm&lt;/code&gt; came up with results unrelated to the activation logging. Huh, I thought. So where exactly &lt;em&gt;was&lt;/em&gt; whether to print colored output determined?&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;# https://github.com/nix-community/home-manager/blob/8a175a89137fe798b33c476d4dae17dba5fb3fd3/lib/bash/home-manager.sh#L22-L34

# Enable colors for terminals, and allow opting out.
if [[ ! -v NO_COLOR &amp;#x26;&amp;#x26; -t 1 ]]; then
	# See if it supports colors.
	local ncolors
	ncolors=$(tput colors 2&gt; /dev/null || echo 0)

	if [[ -n &quot;$ncolors&quot; &amp;#x26;&amp;#x26; &quot;$ncolors&quot; -ge 8 ]]; then
		normalColor=&quot;$(tput sgr0)&quot;
		errorColor=&quot;$(tput bold)$(tput setaf 1)&quot;
		warnColor=&quot;$(tput setaf 3)&quot;
		noteColor=&quot;$(tput bold)$(tput setaf 6)&quot;
	fi
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was pretty straightforward. The &lt;a href=&quot;https://linux.die.net/man/1/tput&quot;&gt;tput&lt;/a&gt; utility reads from terminfo databases to print information about terminal capabilities and features, such as ANSI colors. Here, as &lt;a href=&quot;https://linux.die.net/man/5/terminfo&quot;&gt;terminfo(5)&lt;/a&gt; helpfully tells us,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The numeric capabilities &lt;strong&gt;colors&lt;/strong&gt; and &lt;strong&gt;pairs&lt;/strong&gt; specify the maximum numbers of colors and color-pairs that can be displayed simultaneously.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I ran &lt;code&gt;tput colors&lt;/code&gt; in Ghostty to test if it returned a valid value.&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ tput colors
256&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It did! I also tried testing the if condition in bash, and the result was the same. It was a TTY, &lt;code&gt;NO_COLOR&lt;/code&gt; wasn’t set, and it supported a 256-color lookup table. So what on Earth was causing disabled colors in logging?&lt;/p&gt;
&lt;p&gt;All of a sudden, it occurred to me. From my time spent writing morlana, I knew that the activate step in &lt;code&gt;darwin-rebuild switch&lt;/code&gt; or &lt;code&gt;morlana switch&lt;/code&gt; used sudo to execute the activate script with elevated privileges.&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;# https://github.com/LnL7/nix-darwin/blob/76559183801030451e200c90a1627c1d82bb4910/pkgs/nix-tools/darwin-rebuild.sh#L247-L251

if [ &quot;$USER&quot; != root ]; then
	sudo &quot;$systemConfig/activate&quot;
else
	&quot;$systemConfig/activate&quot;
fi&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;// https://github.com/ryanccn/morlana/blob/a3cabed7e0824b0fda55652628c102ab7a108c2f/src/stages/activate.rs#L23-L34

pub fn activate(out: &amp;#x26;Path) -&gt; Result&amp;#x3C;()&gt; {
    if !util::sudo_cmd(out.join(&quot;activate&quot;))
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()?
        .success()
    {
        bail!(&quot;failed to activate&quot;);
    }


    Ok(())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Was it possible that sudo was somehow interfering with the colors to make them not work? To validate this hypothesis, I tried running &lt;code&gt;sudo tput colors&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sudo tput colors
tput: unknown terminal &quot;xterm-ghostty&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sudo &lt;em&gt;was&lt;/em&gt; causing the issues, and now I also know why: commands that are running inside sudo do not recognize &lt;code&gt;xterm-ghostty&lt;/code&gt;, because they don’t have access to the Ghostty terminfo!&lt;/p&gt;
&lt;p&gt;In a misguided effort to rectify this problem, I enabled the &lt;code&gt;sudo&lt;/code&gt; wrapping feature in Ghostty’s shell integration, which preserves Ghostty terminfo. Now &lt;code&gt;sudo tput colors&lt;/code&gt; printed &lt;code&gt;256&lt;/code&gt; as well! But Home Manager still did not print colored logs. As it turns out, what should’ve been obvious to me in the first place was that shell integration was modifying the shell and creating a wrapper function in the shell, while the &lt;code&gt;sudo&lt;/code&gt; that &lt;code&gt;activate&lt;/code&gt; was running under was the executable, not the function.&lt;/p&gt;
&lt;p&gt;So, I looked into Ghostty’s source to find out what the shell integration was doing in order to figure out how to make this work for sudo anywhere.&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-fish&quot;&gt;# https://github.com/ghostty-org/ghostty/blob/12bf107bcbac28202c5fab828416e8aa43b1b798/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish#L74-L93

# Wrap `sudo` command to ensure Ghostty terminfo is preserved
function sudo -d &quot;Wrap sudo to preserve terminfo&quot;
	set --local sudo_has_sudoedit_flags &quot;no&quot;
	for arg in $argv
		# Check if argument is &#39;-e&#39; or &#39;--edit&#39; (sudoedit flags)
		if string match -q -- &quot;-e&quot; &quot;$arg&quot;; or string match -q -- &quot;--edit&quot; &quot;$arg&quot;
			set --local sudo_has_sudoedit_flags &quot;yes&quot;
			break
		end
		# Check if argument is neither an option nor a key-value pair
		if not string match -r -q -- &quot;^-&quot; &quot;$arg&quot;; and not string match -r -q -- &quot;=&quot; &quot;$arg&quot;
			break
		end
	end
	if test &quot;$sudo_has_sudoedit_flags&quot; = &quot;yes&quot;
		command sudo $argv
	else
		command sudo TERMINFO=&quot;$TERMINFO&quot; $argv
	end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As it turns out, it’s very simple! It just keeps the &lt;code&gt;TERMINFO&lt;/code&gt; environment variable in the sudo invocation. Referring again to &lt;a href=&quot;https://linux.die.net/man/5/terminfo&quot;&gt;terminfo(5)&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the environment variable TERMINFO is set, it is interpreted as the pathname of a directory containing the compiled description you are working on. Only that directory is searched.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, keeping the &lt;code&gt;TERMINFO&lt;/code&gt; variable (which Ghostty sets) in sudo should suffice to make colors work again in Home Manager. Since &lt;code&gt;sudo&lt;/code&gt; on macOS defaults to resetting the environment with &lt;code&gt;env_reset&lt;/code&gt;, we need to add a default to &lt;code&gt;/etc/sudoers&lt;/code&gt; that keeps the &lt;code&gt;TERMINFO&lt;/code&gt; environment variable. And accomplishing this is as simple as&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span&gt;Defaults    env_keep += &quot;TERMINFO&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added this magic line of configuration to &lt;code&gt;/etc/sudoers.d/env-keep-terminfo&lt;/code&gt; (since files from &lt;code&gt;/etc/sudoers.d&lt;/code&gt; are included by default in the config), and tried switching to the configuration again. Home Manager’s logs had become cyan and bold, as they are meant to be.&lt;/p&gt;
&lt;p&gt;After writing to this file, I naturally became wracked with guilt over writing it into a file imperatively rather than including this fix as part of my declarative nix-darwin configuration. After a quick search in the nix-darwin reference, I was relieved to see that nix-darwin provided an option for providing extra configuration to sudo: &lt;a href=&quot;https://daiderd.com/nix-darwin/manual/index.html#opt-security.sudo.extraConfig&quot;&gt;&lt;code&gt;security.sudo.extraConfig&lt;/code&gt;&lt;/a&gt;. (It writes to &lt;code&gt;/etc/sudoers.d/10-nix-darwin-extra-config&lt;/code&gt;.)&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  security.sudo.extraConfig = &#39;&#39;
    Defaults    env_keep += &quot;TERMINFO&quot;
  &#39;&#39;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, all of my problems were solved. This was a problem that had been bugging me for a very long time, and I was glad that with a little under an hour’s work, I had managed to come up with a clean fix for it. Hopefully, you who are reading this have found this article modestly helpful as well; cheers!&lt;/p&gt;
</description>
      <pubDate>Tue, 10 Sep 2024 00:00:00 +0000</pubDate>
      <dc:creator>Ryan Cao</dc:creator>
      <guid>https://ryanccn.dev/posts/ghostty-sudo-terminfo/</guid>
    </item>
  </channel>
</rss>
