<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	
<title>ericportis.com — Posts</title>
<link href="https://ericportis.com/posts/"/>
<link rel="self" href="https://ericportis.com/posts/feed/"/>
<updated>2026-02-27T23:30:39-08:00</updated>
<author>
	<name>Eric Portis</name>
	<email>e@ericportis.com</email>
</author>
<id>https://ericportis.com/posts/</id>
<icon>/apple-touch-icon.png</icon>
	


<entry>
	<title>Monochrome Summer</title>
	<link rel="alternate" href="https://ericportis.com/posts/2026/monochrome-summer/" />
	<id>tag:ericportis.com,2026-02-04:/posts/2026/monochrome-summer/</id>
	<updated>2026-02-04T00:00:00-08:00</updated>
	<content type="html">
	    &lt;style&gt;
@keyframes blink {
  0% {
    opacity: 1;
  }
  49% {
    opacity: 1;
  }
  51% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

.blink {
  animation: blink 0.666s infinite;
}
&lt;/style&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC9906/8th.avif 576w, /assets/2026-02-04-monochrome-summer/DSC9906.avif 4608w, /assets/2026-02-04-monochrome-summer/DSC9906/half.avif 2304w, /assets/2026-02-04-monochrome-summer/DSC9906/quarter.avif 1152w, /assets/2026-02-04-monochrome-summer/DSC9906/16th.avif 288w, /assets/2026-02-04-monochrome-summer/DSC9906/32nd.avif 144w&quot;
        sizes=&quot;(min-width: 576px) 50vw, 100vw&quot;
        width=&quot;4608&quot;
        height=&quot;3702&quot;
      &gt;
      &lt;img id=&apos;DSC9906&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC9906/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC9906/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC9906.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC9906/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC9906/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC9906/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC9906/32nd.jpg 144w&apos;
      sizes=&quot;(min-width: 576px) 50vw, 100vw&quot; alt=&quot;A patch of a couple dozen bright white daisies, in bloom, photographed from above&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC9993/8th.avif 576w, /assets/2026-02-04-monochrome-summer/DSC9993.avif 4608w, /assets/2026-02-04-monochrome-summer/DSC9993/half.avif 2304w, /assets/2026-02-04-monochrome-summer/DSC9993/quarter.avif 1152w, /assets/2026-02-04-monochrome-summer/DSC9993/16th.avif 288w, /assets/2026-02-04-monochrome-summer/DSC9993/32nd.avif 144w&quot;
        sizes=&quot;(min-width: 576px) 50vw, 100vw&quot;
        width=&quot;4608&quot;
        height=&quot;3702&quot;
      &gt;
      &lt;img id=&apos;DSC9993&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC9993/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC9993/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC9993.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC9993/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC9993/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC9993/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC9993/32nd.jpg 144w&apos;
      sizes=&quot;(min-width: 576px) 50vw, 100vw&quot; alt=&quot;A starfish in some watery sand, next to a reflected silhouette of the photographer&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;3072&quot;
        height=&quot;4608&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC0234-Edit/8th.avif 384w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit.avif 3072w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/half.avif 1536w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/quarter.avif 768w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/16th.avif 192w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/32nd.avif 96w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;DSC0234-Edit&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0234-Edit/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0234-Edit/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0234-Edit/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A woman (Britt), small, standing in front of a big granite rock face with lots of texture&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;3072&quot;
        height=&quot;4608&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC0786/8th.avif 384w, /assets/2026-02-04-monochrome-summer/DSC0786.avif 3072w, /assets/2026-02-04-monochrome-summer/DSC0786/half.avif 1536w, /assets/2026-02-04-monochrome-summer/DSC0786/quarter.avif 768w, /assets/2026-02-04-monochrome-summer/DSC0786/16th.avif 192w, /assets/2026-02-04-monochrome-summer/DSC0786/32nd.avif 96w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;DSC0786&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0786/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0786/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0786.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0786/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0786/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0786/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0786/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;The peak of a distant mountain peeking through a gap in some foreground trees&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC9980&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC9980/8th.jpg&apos; 
     width=&apos;4479&apos; 
     height=&apos;2986&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC9980/8th.jpg 560w, /assets/2026-02-04-monochrome-summer/DSC9980.jpg 4479w, /assets/2026-02-04-monochrome-summer/DSC9980/half.jpg 2240w, /assets/2026-02-04-monochrome-summer/DSC9980/quarter.jpg 1120w, /assets/2026-02-04-monochrome-summer/DSC9980/16th.jpg 280w, /assets/2026-02-04-monochrome-summer/DSC9980/32nd.jpg 140w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Four people walking on a beach. They&apos;re in cold-weather clothing; it&apos;s misty, there are a couple of large rock formations sticking out of the water in the distance.&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0162-Pano&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0162-Pano/8th.jpg&apos; 
     width=&apos;4834&apos; 
     height=&apos;8152&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0162-Pano/8th.jpg 604w, /assets/2026-02-04-monochrome-summer/DSC0162-Pano.jpg 4834w, /assets/2026-02-04-monochrome-summer/DSC0162-Pano/half.jpg 2417w, /assets/2026-02-04-monochrome-summer/DSC0162-Pano/quarter.jpg 1209w, /assets/2026-02-04-monochrome-summer/DSC0162-Pano/16th.jpg 302w, /assets/2026-02-04-monochrome-summer/DSC0162-Pano/32nd.jpg 151w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A large triangular rock formation (aka haystack) sticking out of the surf, photographed from a beach. The haystack has a couple of trees growing out of it, has a narrow smaller formation next to it, and is reflected in the wet sand.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0021&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0021/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0021/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC0021.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC0021/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC0021/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC0021/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC0021/32nd.jpg 144w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;The front half of a foot, in a sandal, photographed from above, standing on top of a bed of seaweed.&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;4608&quot;
        height=&quot;3072&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC9914/8th.avif 576w, /assets/2026-02-04-monochrome-summer/DSC9914.avif 4608w, /assets/2026-02-04-monochrome-summer/DSC9914/half.avif 2304w, /assets/2026-02-04-monochrome-summer/DSC9914/quarter.avif 1152w, /assets/2026-02-04-monochrome-summer/DSC9914/16th.avif 288w, /assets/2026-02-04-monochrome-summer/DSC9914/32nd.avif 144w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;DSC9914&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC9914/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC9914/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC9914.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC9914/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC9914/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC9914/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC9914/32nd.jpg 144w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A fern patch, with some patches of sunlight reflecting off of the fronds&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0824&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0824/quarter.jpg&apos; 
     width=&apos;2948&apos; 
     height=&apos;4059&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0824/quarter.jpg 737w, /assets/2026-02-04-monochrome-summer/DSC0824.jpg 2948w, /assets/2026-02-04-monochrome-summer/DSC0824/half.jpg 1474w, /assets/2026-02-04-monochrome-summer/DSC0824/8th.jpg 369w, /assets/2026-02-04-monochrome-summer/DSC0824/16th.jpg 184w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt walking through a field in front of a very tall-looking stand of firs&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0230&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0230/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0230/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0230.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0230/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0230/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0230/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0230/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A gigantic snow-capped mountain (Mt Baker), snow covered and shining in the sunlight&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;


&lt;img id=&apos;DSC0917&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0917/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0917/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC0917.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC0917/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC0917/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC0917/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC0917/32nd.jpg 144w&apos;
      class=&quot;wide&quot; loading=&quot;lazy&quot; sizes=&quot;auto, 100vw&quot; alt=&quot;Britt, with her body facing away but with her face turned back towards the camera - eyes closed, smiling - in front of a jagged mountain range in the distance&quot; /&gt;


&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0312&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0312/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0312/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0312.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0312/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0312/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0312/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0312/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A man (Justin) stands agape in the foreground, pantomiming amazement, while a very modestly-sized 80s-looking piece of decorative architecture (the Sunsphere) sits behind him in the middle distance&quot; /&gt;

  &lt;/div&gt;
  &lt;div style=&quot;display: grid;&quot;&gt;
    &lt;img id=&apos;1&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0655-Animated/1/quarter.jpg&apos; 
     width=&apos;2191&apos; 
     height=&apos;3007&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0655-Animated/1/quarter.jpg 548w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/1.jpg 2191w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/1/half.jpg 1096w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/1/8th.jpg 274w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/1/16th.jpg 137w&apos;
      class=&quot;blink&quot; loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;&quot; style=&quot;grid-column: 1/1; grid-row: 1/1;&quot; /&gt;

    &lt;img id=&apos;2&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0655-Animated/2/quarter.jpg&apos; 
     width=&apos;2191&apos; 
     height=&apos;3007&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0655-Animated/2/quarter.jpg 548w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/2.jpg 2191w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/2/half.jpg 1096w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/2/8th.jpg 274w, /assets/2026-02-04-monochrome-summer/DSC0655-Animated/2/16th.jpg 137w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt jumping, arms raised, in front of a rock wall.&quot; style=&quot;grid-column: 1/1; grid-row: 1/1;&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;160002&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/160002/quarter.jpg&apos; 
     width=&apos;3024&apos; 
     height=&apos;2005&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/160002/quarter.jpg 756w, /assets/2026-02-04-monochrome-summer/160002.jpg 3024w, /assets/2026-02-04-monochrome-summer/160002/half.jpg 1512w, /assets/2026-02-04-monochrome-summer/160002/8th.jpg 378w, /assets/2026-02-04-monochrome-summer/160002/16th.jpg 189w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A man (me), crouching under a semi-circular twisted branch, on a cliff above the sea&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC9907&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC9907/quarter.jpg&apos; 
     width=&apos;4228&apos; 
     height=&apos;2819&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC9907/quarter.jpg 1057w, /assets/2026-02-04-monochrome-summer/DSC9907.jpg 4228w, /assets/2026-02-04-monochrome-summer/DSC9907/half.jpg 2114w, /assets/2026-02-04-monochrome-summer/DSC9907/8th.jpg 529w, /assets/2026-02-04-monochrome-summer/DSC9907/16th.jpg 264w, /assets/2026-02-04-monochrome-summer/DSC9907/32nd.jpg 132w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Three deer laying in a grassy lawn, dotted with clover flowers. An older-looking house sits behind them.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0571&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0571/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0571/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC0571.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC0571/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC0571/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC0571/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC0571/32nd.jpg 144w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Dramatic clouds at sunset, above some dark, moundy-looking mountains&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0302&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0302/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0302/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC0302.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC0302/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC0302/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC0302/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC0302/32nd.jpg 144w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Two women photographed from behind, scooting on scooters down a main street in a downtown area. In the oncoming lane is a weird-looking three wheeled vehicle.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;3072&quot;
        height=&quot;4608&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC9905/8th.avif 384w, /assets/2026-02-04-monochrome-summer/DSC9905.avif 3072w, /assets/2026-02-04-monochrome-summer/DSC9905/half.avif 1536w, /assets/2026-02-04-monochrome-summer/DSC9905/quarter.avif 768w, /assets/2026-02-04-monochrome-summer/DSC9905/16th.avif 192w, /assets/2026-02-04-monochrome-summer/DSC9905/32nd.avif 96w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;DSC9905&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC9905/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC9905/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC9905.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC9905/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC9905/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC9905/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC9905/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;String lights hanging from a tree in a garden&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
  &lt;div style=&quot;display: grid;&quot;&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;2048&quot;
        height=&quot;3072&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/8th.avif 256w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1.avif 2048w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/half.avif 1024w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/quarter.avif 512w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/16th.avif 128w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;1&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/quarter.jpg&apos; 
     width=&apos;2048&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/quarter.jpg 512w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1.jpg 2048w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/half.jpg 1024w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/8th.jpg 256w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/1/16th.jpg 128w&apos;
      class=&quot;blink&quot; loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;&quot; style=&quot;grid-column: 1/1; grid-row: 1/1;&quot; /&gt;

    &lt;/picture&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;2048&quot;
        height=&quot;3072&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/8th.avif 256w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2.avif 2048w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/half.avif 1024w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/quarter.avif 512w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/16th.avif 128w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;2&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/quarter.jpg&apos; 
     width=&apos;2048&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/quarter.jpg 512w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2.jpg 2048w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/half.jpg 1024w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/8th.jpg 256w, /assets/2026-02-04-monochrome-summer/DSC0691-Animated/2/16th.jpg 128w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Bright reflected spots of light, shimmering off a body of water&quot; style=&quot;grid-column: 1/1; grid-row: 1/1;&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0706&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0706/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0706/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0706.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0706/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0706/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0706/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0706/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A dark silhouette of a conifer, in front of a dark late-evening sky and some mountains&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;3072&quot;
        height=&quot;4608&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC0325/8th.avif 384w, /assets/2026-02-04-monochrome-summer/DSC0325.avif 3008w, /assets/2026-02-04-monochrome-summer/DSC0325/half.avif 1536w, /assets/2026-02-04-monochrome-summer/DSC0325/quarter.avif 768w, /assets/2026-02-04-monochrome-summer/DSC0325/16th.avif 192w, /assets/2026-02-04-monochrome-summer/DSC0325/32nd.avif 96w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;DSC0325&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0325/8th.jpg&apos; 
     width=&apos;3008&apos; 
     height=&apos;4512&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0325/8th.jpg 376w, /assets/2026-02-04-monochrome-summer/DSC0325.jpg 3008w, /assets/2026-02-04-monochrome-summer/DSC0325/half.jpg 1504w, /assets/2026-02-04-monochrome-summer/DSC0325/quarter.jpg 752w, /assets/2026-02-04-monochrome-summer/DSC0325/16th.jpg 188w, /assets/2026-02-04-monochrome-summer/DSC0325/32nd.jpg 94w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;White puffy clouds floating in a big (presumably blue) sky behind a metal swinging cattle gate.&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0070&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0070/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0070/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0070.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0070/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0070/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0070/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0070/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt, photographed from behind, climbing over barnacled rocks on a beach with a big haystack in the background. She&apos;s pretty close to the camera, and about to prop herself up with her left hand, which is fully extended.&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0074&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0074/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0074/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0074.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0074/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0074/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0074/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0074/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt climbing haystacks again, in a photo that looks like it was taken a few seconds after the last one. She is further away, still carefully balancing. Also the photo is much brighter than the last one.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0376&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0376/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0376/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC0376.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC0376/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC0376/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC0376/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC0376/32nd.jpg 96w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt turning around to look at the camera while walking ahead of the camera, hiking next to a man (Justin), behind a larger group of hikers.&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0362&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0362/quarter.jpg&apos; 
     width=&apos;2576&apos; 
     height=&apos;3864&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0362/quarter.jpg 644w, /assets/2026-02-04-monochrome-summer/DSC0362.jpg 2576w, /assets/2026-02-04-monochrome-summer/DSC0362/half.jpg 1288w, /assets/2026-02-04-monochrome-summer/DSC0362/8th.jpg 322w, /assets/2026-02-04-monochrome-summer/DSC0362/16th.jpg 161w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt standing and smiling for a portrait, smiling, toes pointed to the sky, curled.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;picture&gt;
  &lt;source
    media=&quot;(dynamic-range: high)&quot;
    type=&quot;image/avif&quot;
    width=&quot;11334&quot;
    height=&quot;4641&quot;
    srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC0370-Pano/8th.avif 1417w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano.avif 11334w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/half.avif 5667w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/quarter.avif 2834w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/16th.avif 708w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/32nd.avif 354w,
    /assets/2026-02-04-monochrome-summer/DSC0370-Pano/64th.avif 177w&quot;
    sizes=&quot;auto, 100vw&quot;
  &gt;
  &lt;img id=&apos;DSC0370-Pano&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0370-Pano/16th.jpg&apos; 
     width=&apos;11334&apos; 
     height=&apos;4641&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0370-Pano/16th.jpg 708w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano.jpg 11334w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/half.jpg 5667w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/quarter.jpg 2834w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/8th.jpg 1417w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/32nd.jpg 354w, /assets/2026-02-04-monochrome-summer/DSC0370-Pano/64th.jpg 177w&apos;
      class=&quot;wide&quot; loading=&quot;lazy&quot; sizes=&quot;auto, 100vw&quot; alt=&quot;A wide panorama of a forested mountain ridge, whose crest is covered by clouds and fog&quot; /&gt;

&lt;/picture&gt;

&lt;img id=&apos;DSC0394-Pano&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0394-Pano/16th.jpg&apos; 
     width=&apos;14099&apos; 
     height=&apos;4616&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0394-Pano/16th.jpg 881w, /assets/2026-02-04-monochrome-summer/DSC0394-Pano.jpg 14099w, /assets/2026-02-04-monochrome-summer/DSC0394-Pano/half.jpg 7050w, /assets/2026-02-04-monochrome-summer/DSC0394-Pano/quarter.jpg 3525w, /assets/2026-02-04-monochrome-summer/DSC0394-Pano/8th.jpg 1762w, /assets/2026-02-04-monochrome-summer/DSC0394-Pano/32nd.jpg 441w, /assets/2026-02-04-monochrome-summer/DSC0394-Pano/64th.jpg 220w&apos;
      class=&quot;wide&quot; loading=&quot;lazy&quot; sizes=&quot;auto, 100vw&quot; alt=&quot;A panorama of a bunch of tightly-spaced, thin tree trunks in a forest&quot; /&gt;


&lt;div class=&quot;group three wide&quot;&gt;
  &lt;div&gt;
    &lt;picture&gt;
      &lt;source
        media=&quot;(dynamic-range: high)&quot;
        type=&quot;image/avif&quot;
        width=&quot;3072&quot;
        height=&quot;4608&quot;
        srcset=&quot;/assets/2026-02-04-monochrome-summer/DSC1191/8th.avif 384w, /assets/2026-02-04-monochrome-summer/DSC1191.avif 3072w, /assets/2026-02-04-monochrome-summer/DSC1191/half.avif 1536w, /assets/2026-02-04-monochrome-summer/DSC1191/quarter.avif 768w, /assets/2026-02-04-monochrome-summer/DSC1191/16th.avif 192w, /assets/2026-02-04-monochrome-summer/DSC1191/32nd.avif 96w&quot;
        sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot;
      &gt;
      &lt;img id=&apos;DSC1191&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC1191/8th.jpg&apos; 
     width=&apos;3072&apos; 
     height=&apos;4608&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC1191/8th.jpg 384w, /assets/2026-02-04-monochrome-summer/DSC1191.jpg 3072w, /assets/2026-02-04-monochrome-summer/DSC1191/half.jpg 1536w, /assets/2026-02-04-monochrome-summer/DSC1191/quarter.jpg 768w, /assets/2026-02-04-monochrome-summer/DSC1191/16th.jpg 192w, /assets/2026-02-04-monochrome-summer/DSC1191/32nd.jpg 96w&apos;
      sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; loading=&quot;lazy&quot; alt=&quot;A bright low sun shining through jungly trees dripping with moss over body of water&quot; /&gt;

    &lt;/picture&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;160001&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/160001/quarter.jpg&apos; 
     width=&apos;2005&apos; 
     height=&apos;3024&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/160001/quarter.jpg 501w, /assets/2026-02-04-monochrome-summer/160001.jpg 2005w, /assets/2026-02-04-monochrome-summer/160001/half.jpg 1003w, /assets/2026-02-04-monochrome-summer/160001/8th.jpg 251w, /assets/2026-02-04-monochrome-summer/160001/16th.jpg 125w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt and I selfie by the water&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC0504-Pano&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC0504-Pano/8th.jpg&apos; 
     width=&apos;4471&apos; 
     height=&apos;6508&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC0504-Pano/8th.jpg 559w, /assets/2026-02-04-monochrome-summer/DSC0504-Pano.jpg 4471w, /assets/2026-02-04-monochrome-summer/DSC0504-Pano/half.jpg 2236w, /assets/2026-02-04-monochrome-summer/DSC0504-Pano/quarter.jpg 1118w, /assets/2026-02-04-monochrome-summer/DSC0504-Pano/16th.jpg 279w, /assets/2026-02-04-monochrome-summer/DSC0504-Pano/32nd.jpg 140w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;A glowy forest. The ground is carpeted with moss and the tree trunks are blooming with light shining between them.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;img id=&apos;DSC1158&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC1158/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC1158/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC1158.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC1158/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC1158/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC1158/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC1158/32nd.jpg 144w&apos;
      class=&quot;wide&quot; loading=&quot;lazy&quot; sizes=&quot;auto, 100vw&quot; alt=&quot;Britt, in a kayak, on a glassy swamp, tipping her baseball cap, beneath a canopy of large trees.&quot; /&gt;


&lt;div class=&quot;group two wide&quot;&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC1009&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC1009/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC1009/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC1009.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC1009/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC1009/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC1009/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC1009/32nd.jpg 144w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Me, in a hotel room, photographing myself in the mirror and Britt getting ready behind me.&quot; /&gt;

  &lt;/div&gt;
  &lt;div&gt;
    &lt;img id=&apos;DSC1184&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/DSC1184/8th.jpg&apos; 
     width=&apos;4608&apos; 
     height=&apos;3072&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/DSC1184/8th.jpg 576w, /assets/2026-02-04-monochrome-summer/DSC1184.jpg 4608w, /assets/2026-02-04-monochrome-summer/DSC1184/half.jpg 2304w, /assets/2026-02-04-monochrome-summer/DSC1184/quarter.jpg 1152w, /assets/2026-02-04-monochrome-summer/DSC1184/16th.jpg 288w, /assets/2026-02-04-monochrome-summer/DSC1184/32nd.jpg 144w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 576px) 50vw, 100vw&quot; alt=&quot;Britt standing on a paved path at sunset, with some sunglasses and a neutral expression. Behind her are rows of trees bathed in glowy light. The photograph is rather grainy.&quot; /&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;img id=&apos;160006&apos; 
     src=&apos;/assets/2026-02-04-monochrome-summer/160006/quarter.jpg&apos; 
     width=&apos;3024&apos; 
     height=&apos;2005&apos; 
     srcset=&apos;/assets/2026-02-04-monochrome-summer/160006/quarter.jpg 756w, /assets/2026-02-04-monochrome-summer/160006.jpg 3024w, /assets/2026-02-04-monochrome-summer/160006/half.jpg 1512w, /assets/2026-02-04-monochrome-summer/160006/8th.jpg 378w, /assets/2026-02-04-monochrome-summer/160006/16th.jpg 189w&apos;
      class=&quot;wide&quot; loading=&quot;lazy&quot; sizes=&quot;auto, 100vw&quot; alt=&quot;Four camp chairs on a grassy knoll, facing a cover, and the sea, and some islands. Three of them are occupied, with people watching the water. One is empty.&quot; /&gt;



	</content>
</entry>



<entry>
	<title>When do you have to repeat redundant sizes attributes within a &amp;lt;picture&amp;gt;?</title>
	<link rel="alternate" href="https://ericportis.com/posts/2025/when-do-you-have-to-repeat-sizes/" />
	<id>tag:ericportis.com,2025-08-15:/posts/2025/when-do-you-have-to-repeat-sizes/</id>
	<updated>2025-08-15T00:00:00-07:00</updated>
	<content type="html">
	    &lt;link rel=&quot;stylesheet&quot; href=&quot;/css/syntax-highlight.css&quot; /&gt;

&lt;style&gt;
pre {
	background-color: #f1f1f1;
}
pre code {
	background-color: transparent;
}
&lt;/style&gt;

&lt;h2 class=&quot;little-h&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;Almost always, but that &lt;a href=&quot;https://github.com/whatwg/html/pull/6695&quot;&gt;might be changing&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;Okay, so—&lt;/h2&gt;

&lt;p&gt;Let’s say you’ve got a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element that includes multiple candidate sources. If you’re doing &lt;a href=&quot;https://www.w3.org/TR/respimg-usecases/#art-direction&quot;&gt;art direction&lt;/a&gt;, those sources are probably each going to be laid out differently. This means they’ll each need their own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; attribute.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- landscape image over text --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;media=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(min-width: 600px)&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;landscape-lg.jpg 1920w, landscape-sm.jpg 960w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(max-width: 960px) 960px, 90vw&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- square crop next to text --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;square-lg.jpg 480w, square-sm.jpg 240w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;25vw&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This pattern was generalized into a universal requirement: any time an element (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;source&amp;gt;&lt;/code&gt;) had a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;srcset&lt;/code&gt; that included &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt; descriptors, it also needed its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;media&lt;/code&gt;-switching that &lt;em&gt;isn’t&lt;/em&gt; related to layout changes (e.g., responding to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefers-contrast&lt;/code&gt;), or for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;-switching use case, this requirement just leads to repetition.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;image/jxl&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lg.jxl 1000w, sm.jxl 500w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(max-width: 960px) 768px, 80vw&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lg.jpg 1000w, sm.jpg 500w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(max-width: 960px) 768px, 80vw&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When you’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt;, it doesn’t even make sense in the art direction case, because the browser is just going to figure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; out on its own.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- landscape image over text --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;media=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(min-width: 600px)&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;landscape-lg.jpg 1920w, landscape-sm.jpg 960w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;auto&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- square crop next to text --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loading=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lazy&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;480&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;height=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;480&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;square-lg.jpg 480w, square-sm.jpg 240w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;auto&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Thankfully, &lt;a href=&quot;https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:allows-auto-sizes:~:text=If%20the%20img%20element%20allows%20auto%2Dsizes%2C%20then%20the%20sizes%20attribute%20can%20be%20omitted%20on%20previous%20sibling%20source%20elements&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; has been special-cased!&lt;/a&gt; If the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; value is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto&lt;/code&gt;, you don’t need to duplicate it across your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;source&amp;gt;&lt;/code&gt;s. The previous example works the same in Chrome with or without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;source&amp;gt;&lt;/code&gt;. Yay!&lt;/p&gt;

&lt;p&gt;Unfortunately,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; and its special-cased handling &lt;a href=&quot;https://caniuse.com/mdn-html_elements_img_sizes_auto&quot;&gt;aren’t supported everywhere yet&lt;/a&gt;. So in this example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;image/avif&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;large.avif 2000w, small.avif 1000w&quot;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loading=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lazy&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2000&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;height=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2000&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;srcset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;large.jpg 2000w, small.jpg 1000w&quot;&lt;/span&gt;
&lt;span class=&quot;hll&quot;&gt;    &lt;span class=&quot;na&quot;&gt;sizes=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;auto, 50vw&quot;&lt;/span&gt;
&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Safari and Firefox will always use the last-resort &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;100vw&lt;/code&gt; fallback as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; value, after selecting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;source type=&quot;image/avif&quot;&amp;gt;&lt;/code&gt;. Even though we put a better fallback length in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img sizes&amp;gt;&lt;/code&gt;, just for them!&lt;/p&gt;

&lt;p&gt;Boo! This caught me out recently, and so here I am, blogging about it.&lt;/p&gt;

&lt;p&gt;I think two things should happen:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Safari and Firefox should implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt;, &lt;em&gt;post haste.&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;The spec should be updated so that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; is used as a fallback, if the selected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;source&amp;gt;&lt;/code&gt; doesn’t have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; of its own. This isn’t only useful in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; case, it’s also useful for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;-switching, and in any other situation where the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; values within a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; would otherwise repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m not the first person to have that idea. &lt;a href=&quot;https://github.com/whatwg/html/issues/6633&quot;&gt;Jake Archibald had it four years ago.&lt;/a&gt; After some discussion, a handful of relevant parties seemed to agree. Jake even &lt;a href=&quot;https://github.com/whatwg/html/pull/6695&quot;&gt;started a PR&lt;/a&gt;. So… let’s do it!&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;Update!&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/whatwg/html/pull/6695#issuecomment-3191886604&quot;&gt;Looks like Jake is working on the PR again.&lt;/a&gt; Yay!&lt;/p&gt;

	</content>
</entry>



<entry>
	<title>I finished a 50K</title>
	<link rel="alternate" href="https://ericportis.com/posts/2025/orcas-50k/" />
	<id>tag:ericportis.com,2025-08-12:/posts/2025/orcas-50k/</id>
	<updated>2025-08-12T00:00:00-07:00</updated>
	<content type="html">
	    &lt;style&gt;
aside.footnotes {
	font-size: 0.8625em;
	background: #ddd;
	padding: 1em;
	border-radius: 1em;
}

.inputContainer {
	padding: 0.5em;
	background: white;
	border-radius: 0.25em;
	width: max-content;
	margin: 1em auto;
}
fieldset {
	border: none;
}
label + input {
	margin-left: 1em;
}

output {
	font-family: Monaco, monospaced;
	width: 5ch;
	overflow: clip;
	font-size: 0.8em;
}

&lt;/style&gt;

&lt;p&gt;In June I ran the &lt;a href=&quot;https://www.rainshadowrunning.com/orcas50k25k.html&quot;&gt;Orcas Island 50K&lt;/a&gt;. When I finished the race I also finished a 2+ year journey, which I’d started, in part, because I was staring down 40-years-old and had &lt;em&gt;feelings&lt;/em&gt; about &lt;em&gt;aging&lt;/em&gt; and my &lt;em&gt;body&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I signed up for the 2024 edition of the race way back in 2023. I started training early. Then, I sprained my ankle. I rested, recovered – and sprained it again! I’d thought running &lt;em&gt;uphill&lt;/em&gt; was going to be the hard part of all of this, but once I learned to slow down and &lt;a href=&quot;https://www.polar.com/us-en/sensors/h9-heart-rate-sensor&quot;&gt;listen to my heart&lt;/a&gt; – it was the downs that got me.&lt;/p&gt;

&lt;p&gt;Then I broke my pinky toe (on a donut run&lt;sup id=&quot;fn-1-mark&quot;&gt;&lt;a href=&quot;#fn-1-note&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;

&lt;p&gt;That series of injuries left me quite behind on my training. I tried to “catch up”, acquired a predictable case of &lt;a href=&quot;https://en.wikipedia.org/wiki/Patellofemoral_pain_syndrome&quot;&gt;runner’s knee&lt;/a&gt;, turned 40, and spent the 2024 race sitting behind a desk, volunteering:&lt;/p&gt;

&lt;figure&gt;
&lt;picture&gt;
&lt;source type=&quot;image/jxl&quot; sizes=&quot;auto, (min-width: 1152px) 768px, 80vw&quot; srcset=&quot;
		/assets/2025-08-12-orcas-50k/1D4A0107-X5/jxl/1920.jxl 1920w,
		/assets/2025-08-12-orcas-50k/1D4A0107-X5/jxl/1280.jxl 1280w,
		/assets/2025-08-12-orcas-50k/1D4A0107-X5/jxl/960.jxl 960w
	&quot; /&gt;
&lt;img width=&quot;1920&quot; height=&quot;1280&quot; loading=&quot;lazy&quot; sizes=&quot;auto, (min-width: 1152px) 768px, 80vw&quot; srcset=&quot;
		/assets/2025-08-12-orcas-50k/1D4A0107-X5/jpeg/1920.jpg 1920w,
		/assets/2025-08-12-orcas-50k/1D4A0107-X5/jpeg/1280.jpg 1280w,
		/assets/2025-08-12-orcas-50k/1D4A0107-X5/jpeg/960.jpg 960w
	&quot; src=&quot;/assets/2025-08-12-orcas-50k/1D4A0107-X5/jpeg/1280.jpg&quot; alt=&quot;Me, in a flannel, sitting behind a table, handing a racer their bib. I’m wearing a serious expression.&quot; /&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Photo by &lt;a href=&quot;https://www.somerphoto.com&quot;&gt;Somer Kreisman&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Try, fail, try again. Here I am in 2025:&lt;/p&gt;

&lt;figure&gt;

&lt;picture&gt;
&lt;source type=&quot;image/jxl&quot; sizes=&quot;auto, calc(100vh * (1280/1920))&quot; srcset=&quot;
		/assets/2025-08-12-orcas-50k/B05I5709/jxl/1920.jxl 1280w,
		/assets/2025-08-12-orcas-50k/B05I5709/jxl/1280.jxl 853w,
		/assets/2025-08-12-orcas-50k/B05I5709/jxl/960.jxl 640w
	&quot; /&gt;
&lt;img width=&quot;1280&quot; height=&quot;1920&quot; loading=&quot;lazy&quot; sizes=&quot;auto, calc(100vh * (1280/1920))&quot; srcset=&quot;
		/assets/2025-08-12-orcas-50k/B05I5709/jpeg/1920.jpg 1280w,
		/assets/2025-08-12-orcas-50k/B05I5709/jpeg/1280.jpg 853w,
		/assets/2025-08-12-orcas-50k/B05I5709/jpeg/960.jpg 640w
	&quot; src=&quot;/assets/2025-08-12-orcas-50k/B05I5709/jpeg/960.jpg&quot; alt=&quot;A head-to-toe portrait of me, in three-quarters view, running on a trail on a bright sunny day. There’s a beautiful view of the sea and islands behind me. I’ve got a bright, wide-eyed expression, like the tail end of a smile, and am looking straight ahead.&quot; /&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Photo by &lt;a href=&quot;https://www.somerphoto.com&quot;&gt;Somer Kreisman&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Feelings! I have feelings about aging and my body! And how do we process feelings? With &lt;em&gt;charts&lt;/em&gt;.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;Training&lt;/h2&gt;

&lt;p&gt;Here’s all of my long runs (the race is the last set of bars, on the right):&lt;/p&gt;

&lt;div id=&quot;training-charts&quot;&gt;
&lt;img src=&quot;/assets/2025-08-12-orcas-50k/training.png&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;Someone recently asked me if they should run a marathon. I replied: I don’t know, maybe not? The training takes &lt;em&gt;so much&lt;/em&gt; time. Time &lt;a href=&quot;/posts/2023/the-maintenance-race-and-me#men-sailing-alone&quot;&gt;&lt;em&gt;alone&lt;/em&gt;&lt;/a&gt;. I say I want a life rich with community and yet I spent seven months waving goodbye to my wife on weekend mornings, driving to the woods, and putting one foot in front of the other for hours (before spending the rest of the weekend wrecked).&lt;/p&gt;

&lt;p&gt;So, if you like and/or value other people, maybe don’t pursue a solo endurance sport goal.&lt;/p&gt;

&lt;p&gt;On the other hand! It is rewarding to work steadily at something, for a long time, and watch the impossible slowly become possible. That’s in these charts. They go up and to the right.&lt;/p&gt;

&lt;p&gt;(Mostly.)&lt;/p&gt;

&lt;p&gt;Runner’s knee bit me again in week 17; I spent week 18 icing and running less; when that didn’t fix it I spent week 19 not running at all. I instituted a regular routine of exotic squats. I built back distance and time, then re-introduced climbs and (scariest of all) descents. It went well. I fixed it!&lt;/p&gt;

&lt;p&gt;Encountering and solving problems like that changed how I think about running, what I think it’s &lt;em&gt;for&lt;/em&gt;. When I was younger running felt like a test of character. Success was about, alternately:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;finding flow and getting in “the zone” or,&lt;/li&gt;
  &lt;li&gt;digging deep and pushing through.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Contrary to this, &lt;a href=&quot;https://interconnected.org/home/2025/04/10/marathon&quot;&gt;Matt Webb recently described training for a marathon as a kind of engineering challenge&lt;/a&gt;: learning to understand your body as a system; using that understanding to solve problems; new problems teach you new things about the system. That’s how it went with my knee. That’s how a bunch of things went. Patient research and careful trial-and-error hits different than a Nike commercial, but for a &lt;del&gt;39&lt;/del&gt; &lt;del&gt;40&lt;/del&gt; &lt;ins&gt;41&lt;/ins&gt;-year old, who has feelings about aging and his body? It hits deeper. It is thrilling.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;Race Day&lt;/h2&gt;

&lt;p&gt;So after a more-or-less successful seven-month training cycle, my longest long run had been for just over 80% of the both the 50K’s distance and elevation gain. On the final descent of that longest training run, I contemplated what it would be like to run for six &lt;em&gt;more&lt;/em&gt; miles and climb/descend &lt;em&gt;another&lt;/em&gt; twelve hundred feet. The training plans say you can do it. I wasn’t so sure.&lt;/p&gt;

&lt;p&gt;I did it.&lt;/p&gt;

&lt;p&gt;During those last six miles of the race, here’s what I was thinking:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I am going to finish!&lt;/li&gt;
  &lt;li&gt;I need to slow down.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I did not feel like I was “finishing strong.”&lt;/p&gt;

&lt;p&gt;One fun (?) thing about a race though, is that you get to compare yourself to other people. A lot of other people needed to slow down, too.&lt;/p&gt;

&lt;p&gt;Here’s a &lt;a href=&quot;https://en.wikipedia.org/wiki/Box_plot&quot;&gt;box plot&lt;/a&gt; of the distribution of racers’ paces over the four officially-timed segments, with the median pace marked by a black line and my pace marked in blue:&lt;/p&gt;

&lt;div id=&quot;race-box&quot;&gt;
&lt;img src=&quot;/assets/2025-08-12-orcas-50k/race-box.png&quot; alt=&quot;A box plot of the distributions of the four segments of the race. The first segment had the fastest p25-p75, with paces ranging from 11:40-16 minutes per mile. The median was pace around 13:40 and Eric’s was around 12:50. The second segment had the slowest p25-p75, with paces ranging from just over 15 minutes per mile to 21:30. The median pace was around 17:50 per mile and Eric’s pace was a brisk 15:50. The third segment got faster; the p25-p75 ranged from around 12 minutes to around 16 minutes per mile. The median was around 13:50 and Eric’s pace was around 13:20. The last segment gets noticeably slower again (although not as slow as the second segment). The p25-p75 ranges from around 13:20 to around 17:00; the median pace was 15:10; Eric’s pace was around 14:30.&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;Thoughts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everybody slowed down during the second segment, due to the steep climb up Mt Constitution. I felt strong going up the mountain and it’s cool to see that in the data!&lt;sup id=&quot;fn-2-mark&quot;&gt;&lt;a href=&quot;#fn-2-note&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Most people slowed down during the last segment. I don’t think this was because of the terrain – I think it was because of exhaustion. It’s nice to know that’s normal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distributions are interesting; there were few-enough racers that a chart of everybody’s paces through the four segments is more-or-less legible, too:&lt;/p&gt;

&lt;div id=&quot;race-lines&quot;&gt;
&lt;img src=&quot;/assets/2025-08-12-orcas-50k/race-lines.png&quot; alt=&quot;More than a hudred lines showing the individual paces of each racer over each of the four segments. You can see more individual stories than in the previous chart -- like racers who DNF’d because they did not hit cutoff times after reaching the top of Mt Constitution. The fastest racer is also notable - they were one of the few who got faster over the last segment. In general people got faster or slower together – the order of folks doesn’t change that much, generally – but there are a few outliers (people who got much faster or slower than the people around them from one segment to the next). The transition between the third and last segments involved a lot more chaotic change than the transitions between the earlier segments, as racers either finished strong or hit a wall.&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;I’ll close with the best piece of endurance-running advice I’ve heard: &lt;a href=&quot;https://semi-rad.com/2025/05/a-regular-persons-guide-to-surviving-an-ultramarathon/#:~:text=%E2%80%9CIf%20you%20feel%20bad%2C%20eat%20something.%20If%20you%20feel%20good%2C%20slow%20down.%E2%80%9D&quot;&gt;“If you feel bad, eat something. If you feel good, slow down.”&lt;/a&gt; Advice for life.&lt;/p&gt;

&lt;aside class=&quot;footnotes&quot;&gt;
&lt;ol start=&quot;1&quot;&gt;

&lt;li id=&quot;fn-1-note&quot;&gt;
We were camping with friends, about a mile from our car. I woke up early to hike out and drive into town to buy &lt;a href=&quot;https://www.seabirdbakeshop.com&quot;&gt;the best donuts in the world&lt;/a&gt;, which are only available for a few hours on Saturday mornings. After hiking, driving, waiting in line, buying donuts, and driving/hiking back to camp, I held the best donuts in the world aloft and made a triumphant entrance, exclaiming, “donuts!—&lt;em&gt;OW!&lt;/em&gt;” as I stubbed my toe on a root.

&lt;a href=&quot;#fn-1-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;


&lt;li id=&quot;fn-2-note&quot;&gt;&lt;p&gt;
All credit to my wife. Not only did she materially and emotionally support me along every step of this journey – encouraging me to commit after I half-heartedly floated the idea of signing up, clearing schedules and picking up slack ensuring I had time to train, cheering me on as my training progressed, and listening to and advising me when it didn’t – she surprised me at the base of the race’s biggest climb with leaping cheers, a high five, and a kiss. LFG.&lt;/p&gt;

&lt;p&gt;I couldn’t and wouldn’t have done this race without her. Thank you. &lt;a href=&quot;#fn-2-mark&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;

&lt;/ol&gt;
&lt;/aside&gt;

&lt;script src=&quot;/assets/2025-08-12-orcas-50k/d3.min.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/assets/2025-08-12-orcas-50k/plot.min.js&quot;&gt;&lt;/script&gt;

&lt;script type=&quot;module&quot;&gt;

///////////////////////
// helper functions

const milesToKm = ( miles ) =&gt; miles * 1.60934;
const feetToMeters = ( feet ) =&gt; feet / 3.28084;
const perMileToPerKm = ( perMile ) =&gt; perMile / 1.60934;
const prettyHours = ( hours ) =&gt; {
	const intHours = Math.floor(hours);
	const fractionalHours = hours - intHours;
	const minutes = Math.round( fractionalHours * 60 );
	return `${ intHours } hours, ${ minutes } minutes`;
}
const prettyMinutesAndSeconds = ( seconds ) =&gt; {
	const minutes = Math.floor( seconds / 60 );
	const remainderSeconds = Math.round( seconds - ( minutes * 60 ) );
	return `${ minutes }:${ remainderSeconds.toString().padStart( 2, &apos;0&apos; ) }`;
}
const intlNumberFormat = new Intl.NumberFormat();
const localizeNumber = ( n ) =&gt; intlNumberFormat.format( n );
const prettySegment = (s, units) =&gt; `${s.from} →
${s.to}
${ localizeNumber( ( units === &apos;metric&apos; ?
	Math.round( milesToKm( s.distance ) * 10 ) / 10 :
	Math.round( s.distance * 10 ) / 10 )
) } ${ ( units === &apos;metric&apos; ? &apos;km&apos; : &apos;miles&apos; )}
↗︎ ${ localizeNumber(
	( units === &apos;metric&apos; ?
		Math.round( feetToMeters( s.ascent ) / 10 ) * 10 :
		Math.round( s.ascent / 10 ) * 10 ) 
) } ${ ( units === &apos;metric&apos; ? &apos;meters&apos; : &apos;feet&apos; ) }
↘︎ ${ localizeNumber(
	( units === &apos;metric&apos; ?
		Math.round( feetToMeters( s.descent ) / 10 ) * 10 :
		Math.round( s.descent / 10 ) * 10 )
) } ${ ( units === &apos;metric&apos; ? &apos;meters&apos; : &apos;feet&apos; ) }`;


// before we start messing with the DOM, grab the data
// fetch in parallel but await all results

const [ trainingData, racerPaceData, segmentData, racerPrettyPaces ] = await Promise.all( [

	fetch(&apos;/assets/2025-08-12-orcas-50k/training.json&apos;)
		.then(response =&gt; response.json())
		.catch(error =&gt; console.log(error)),
	fetch(&apos;/assets/2025-08-12-orcas-50k/racerPaces.json&apos;)
		.then(response =&gt; response.json())
		.catch(error =&gt; console.log(error)),
	fetch(&apos;/assets/2025-08-12-orcas-50k/segments.json&apos;)
		.then(response =&gt; response.json())
		.catch(error =&gt; console.log(error)),
	fetch(&apos;/assets/2025-08-12-orcas-50k/racerPrettyPaces.json&apos;)
		.then(response =&gt; response.json())
		.catch(error =&gt; console.log(error))

] );

//just gonna pre-process and paste this in here...
const sortedBibs = [108,146,130,136,62,56,135,1,39,23,53,64,112,47,83,24,4,82,50,125,31,38,49,123,86,105,55,65,52,37,48,18,84,134,87,110,114,44,14,104,26,33,77,152,137,88,15,118,102,66,139,121,115,141,142,75,19,42,117,128,122,58,22,133,147,5,100,76,34,127,35,85,60,144,41,57,90,67,103,119,32,109,28,145,36,124,107,71,126,97,81,46,30,149,72,89,8,7,43,69,132,70,27,3,143,98,74,150,140,96,113,106,80,45,29,131,9,92,68,138,51,95,151]

const mySortedBib = sortedBibs.indexOf(115);


///////////
// setup 
///////////

// training charts...


const trainingChartsDiv = document.querySelector(&apos;#training-charts&apos;);
const raceBoxDiv = document.querySelector(&apos;#race-box&apos;);
const raceLinesDiv = document.querySelector(&apos;#race-lines&apos;);

// clear placeholders
trainingChartsDiv.innerHTML = &apos;&apos;;
raceBoxDiv.innerHTML = &apos;&apos;;
raceLinesDiv.innerHTML = &apos;&apos;;

// training charts div only has one placeholder but it has multiple charts
// which all need their own containers
const distanceChartDiv = document.createElement(&apos;div&apos;);
const climbChartDiv = document.createElement(&apos;div&apos;);
const timeChartDiv = document.createElement(&apos;div&apos;);

trainingChartsDiv.appendChild( distanceChartDiv );
trainingChartsDiv.appendChild( climbChartDiv );
trainingChartsDiv.appendChild( timeChartDiv );

// units input

const unitsForm = document.createElement(&apos;form&apos;);
unitsForm.classList.add(&apos;inputContainer&apos;);
unitsForm.innerHTML = `
	&lt;fieldset class=&quot;info-line&quot;&gt;

		&lt;legend&gt;Units&lt;/legend&gt;
	
		&lt;input type=&quot;radio&quot; id=&quot;imperial&quot; name=&quot;units&quot; value=&quot;imperial&quot; checked /&gt;
		&lt;label for=&quot;imperial&quot;&gt;Imperial&lt;/label&gt;
	
		&lt;input type=&quot;radio&quot; id=&quot;metric&quot; name=&quot;units&quot; value=&quot;metric&quot; /&gt;
		&lt;label for=&quot;metric&quot;&gt;Metric&lt;/label&gt;

	&lt;/fieldset&gt;	
`;

trainingChartsDiv.parentNode.insertBefore( unitsForm, trainingChartsDiv )

// racer input

const racerForm = document.createElement(&apos;form&apos;);
racerForm.classList.add(&apos;inputContainer&apos;);
racerForm.innerHTML = `
	&lt;div class=&quot;info-line&quot;&gt;

		&lt;label for=&quot;racer&quot; style=&quot;&quot;&gt;Racer&lt;/label&gt;

		&lt;input style=&quot;display: block;&quot; type=&quot;range&quot; id=&quot;racer-input&quot; name=&quot;units&quot; min=0 max=${ sortedBibs.length - 1 } step=1 value=&quot;${ mySortedBib }&quot; /&gt;

		&lt;button style=&quot;display: block; margin: auto;&quot; id=&quot;reset-to-eric&quot; type=button&gt;Reset (to Eric)&lt;/button&gt;

	&lt;/div&gt;
`;

// snag some references
const racerRangeInput = racerForm.querySelector(&apos;#racer-input&apos;);
const racerResetButton = racerForm.querySelector(&apos;#reset-to-eric&apos;);

// insert into the dom
raceLinesDiv.parentNode.insertBefore( racerForm, raceLinesDiv )


////////////////////////////////
// functions that render charts

const renderPlot = ( plot, container ) =&gt; {

	// remove container contents
	container.innerHTML = &apos;&apos;;
	
	// add the plot
	container.appendChild( plot );

};

// constants
const siteColors = [ &quot;#0092ff&quot;, &quot;#71a300&quot;, &quot;#d46d00&quot;, &quot;#ea3daa&quot;, &quot;#b08900&quot; ];
const plotHeight = 200;
const plotFallbackWidth = 600;
const plotMarginLeft = 50;
const myBib = 115;

const distancePlot = ( units, width ) =&gt; {

	return Plot.plot({
		height: plotHeight,
		marginLeft: plotMarginLeft,
		width: width,
		y: { 
			label: `Distance (${ units === &apos;metric&apos; ? &apos;kilometers&apos; : &apos;miles&apos; }) ↑`,
			grid: true
		},
		x: {
			label: &quot;Week&quot;,
			grid: true
		},
		marks: [
			Plot.barY( trainingData, {
				x: &quot;week&quot;,
				y: d =&gt; ( units === &apos;metric&apos; ? milesToKm( d.distance ) : d.distance ),
				fill: siteColors[ 0 ],
				title: d =&gt; {
					if (units === &apos;metric&apos;) {
						return `${ Math.round( milesToKm( d.distance ) * 10 ) / 10 } km`;
					}
					return `${ Math.round( d.distance * 10 ) / 10 } miles`;
				}
			} )
		]
	});

}


const climbPlot = ( units, width ) =&gt; {

	return Plot.plot({
		height: plotHeight,
		marginLeft: plotMarginLeft,
		width: width,
		y: { 
			label: `Climb (${ units === &apos;metric&apos; ? &apos;meters&apos; : &apos;feet&apos; }) ↑`,
			grid: true
		},
		x: {
			label: &quot;Week&quot;,
			grid: true
		},
		marks: [
			Plot.barY( trainingData, {
				x: &quot;week&quot;,
				y: d =&gt; ( units === &apos;metric&apos; ? feetToMeters( d.climb ) : d.climb ),
				fill: siteColors[1],
				title: d =&gt; {
					if (units === &apos;metric&apos;) {
						return `${ new Intl.NumberFormat().format( Math.round( feetToMeters( d.climb ) ) ) } meters`;
					}
					return `${ new Intl.NumberFormat().format( Math.round( d.climb ) ) } feet`;
				}
			} )
		]
	});

}

const timePlot = ( width ) =&gt; {

	return Plot.plot({
		height: plotHeight,
		marginLeft: plotMarginLeft,
		width: width,
		y: { 
			label: `Time (hours) ↑`,
			grid: true
		},
		x: {
			label: &quot;Week&quot;,
			grid: true
		},
		marks: [
			Plot.barY( trainingData, {
				x: &quot;week&quot;,
				y: &quot;time&quot;,
				fill: siteColors[3],
				title: d =&gt; prettyHours( d.time )
			} )
		]
	});

}
 
const raceBoxPlot = ( units, width ) =&gt; {
	return Plot.plot({
		width: width,
		marginBottom: 100,
		y: {
			grid: true,
			label: ( units === &apos;metric&apos; ? &apos;Pace (minutes/km) ↑&apos; : &apos;Pace (minutes/mile) ↑&apos; ),
			tickFormat: (d) =&gt; prettyMinutesAndSeconds( d )
		},
		x: {
			label: null,
			tickFormat: (d) =&gt; prettySegment( segmentData[ d - 1 ], units )
		},
	marks: [
		Plot.boxY(racerPaceData, {
			x: &quot;segment&quot;,
			y: d =&gt; ( units === &apos;metric&apos; ? perMileToPerKm( d.pace ) : d.pace ),
			fill: &apos;#bbb&apos;
		} ),
		Plot.tickY( racerPaceData.filter( x =&gt; x.bib === myBib ), {
			x: &quot;segment&quot;,
			y: d =&gt; ( units === &apos;metric&apos; ? perMileToPerKm( d.pace ) : d.pace ),
			stroke: siteColors[0],
			strokeWidth: 2
		} ),
		// invisible rects for tooltips
		Plot.rectY( racerPaceData.filter( x =&gt; x.bib === myBib ), {
			x: &quot;segment&quot;,
			y1: Math.min( ...racerPaceData.map( d =&gt; ( units === &apos;metric&apos; ? perMileToPerKm( d.pace ) : d.pace ) ) ),
			y2: Math.max( ...racerPaceData.map( d =&gt; ( units === &apos;metric&apos; ? perMileToPerKm( d.pace ) : d.pace ) ) ),
			opacity: 0,
			title: d =&gt; {
				const myPace = ( units === &apos;metric&apos; ? perMileToPerKm( d.pace ) : d.pace );
				const sortedPaces = racerPaceData
					.filter( r =&gt; r.segment === d.segment )
					.map( r =&gt; r.pace )
					.sort( (a,b) =&gt; a - b );
				// lazy, good enough?
				const medianPaceMiles = sortedPaces[ Math.floor( sortedPaces.length / 2 ) ];
				const medianPace = ( units === &apos;metric&apos; ? perMileToPerKm( medianPaceMiles ) : medianPaceMiles );
				const descriptor = ( units === &apos;metric&apos; ? &apos;minutes/km&apos; : &apos;minutes/mile&apos; );
				return `Median: ${ prettyMinutesAndSeconds(medianPace) } ${ descriptor }
Me: ${ prettyMinutesAndSeconds(myPace) } ${ descriptor }`

				
			}
		} )
  	]
})
};



const raceLinesPlot = ( units, width, racer ) =&gt; {
	return Plot.plot( {
		width: width,
		marginBottom: 100,
		x: {
			domain: [ 0, 4.2 ],
			label: null,
			ticks: [0.6, 1.6, 2.6, 3.6],
			tickFormat: (d) =&gt; prettySegment( segmentData[ Math.floor(d) ], units )
		},
		y: {
			label: ( units === &apos;metric&apos; ? &apos;Pace (minutes/km) ↑&apos; : &apos;Pace (minutes/mile) ↑&apos; ),
			tickFormat: (d) =&gt; prettyMinutesAndSeconds( d )
		},
		marks: [
			Plot.line(racerPrettyPaces, {
				x: &quot;x&quot;,
				y: &quot;pace&quot;, 
				z: &quot;bib&quot;,
				strokeWidth: (d) =&gt; d.bib === sortedBibs[parseInt(racer)] ? 3 : 1,
				stroke: (d) =&gt; d.bib === sortedBibs[parseInt(racer)] ? siteColors[0] : &apos;rgb(50% 50% 50% / 0.25)&apos;,
				mixBlendMode: &quot;multiply&quot;
			} )
		]
	
	} );
}
 

// when should we actually render the charts?

// when they change size (also when they initially are laid out)
const ro = new ResizeObserver( entries =&gt; {

	for ( const entry of entries ) {

		const units = unitsForm[&apos;units&apos;].value;
		const width = entry.contentBoxSize[0].inlineSize;

		entry.target.setAttribute( &apos;data-width&apos;, width );

		// TODO some easier way to associate divs with their charts and not repeat myself so much?
		if ( entry.target === distanceChartDiv ) {
			renderPlot(
				distancePlot(
					units, 
					width
				),
				distanceChartDiv
			);
		} else if ( entry.target === climbChartDiv ) {
			renderPlot(
				climbPlot(
					units, 
					width
				),
				climbChartDiv
			);
 		} else if ( entry.target === timeChartDiv ) {
			renderPlot(
				timePlot(
					width
				),
				timeChartDiv
			);
 		} else if ( entry.target === raceBoxDiv ) {
			renderPlot(
				raceBoxPlot(
					units, 
					width
				),
				raceBoxDiv
			);
 		} else if ( entry.target === raceLinesDiv ) {
			renderPlot(
				raceLinesPlot(
					units, 
					width,
					racerRangeInput.value
				),
				raceLinesDiv
			);
 		} 
		
	}
} );
ro.observe( distanceChartDiv );
ro.observe( climbChartDiv );
ro.observe( timeChartDiv );
ro.observe( raceBoxDiv );
ro.observe( raceLinesDiv );


// when the units input changes
unitsForm.addEventListener( &apos;change&apos;, () =&gt; {
	renderPlot(
		distancePlot(
			unitsForm[&apos;units&apos;].value, 
			parseInt( distanceChartDiv.getAttribute(&apos;data-width&apos;) ) || plotFallbackWidth
		),
		distanceChartDiv
	);
	renderPlot(
		climbPlot(
			unitsForm[&apos;units&apos;].value, 
			parseInt( climbChartDiv.getAttribute(&apos;data-width&apos;) ) || plotFallbackWidth
		),
		climbChartDiv
	);
	// don&apos;t need to change timePlot, as there are no units there that change
	renderPlot(
		raceBoxPlot(
			unitsForm[&apos;units&apos;].value, 
			parseInt( raceBoxDiv.getAttribute(&apos;data-width&apos;) ) || plotFallbackWidth
		),
		raceBoxDiv
	);
	renderPlot(
		raceLinesPlot(
			unitsForm[&apos;units&apos;].value, 
			parseInt( raceLinesDiv.getAttribute(&apos;data-width&apos;) ) || plotFallbackWidth
		),
		raceLinesDiv
	);
} );

// when the racer input changes
racerRangeInput.addEventListener( &apos;input&apos;, () =&gt; {
	renderPlot(
		raceLinesPlot(
			unitsForm[&apos;units&apos;].value, 
			parseInt( raceLinesDiv.getAttribute(&apos;data-width&apos;) ) || plotFallbackWidth,
			racerRangeInput.value
		),
		raceLinesDiv
	);
} );

racerResetButton.addEventListener( &apos;click&apos;, () =&gt; {
	racerRangeInput.value = mySortedBib;
	renderPlot(
		raceLinesPlot(
			unitsForm[&apos;units&apos;].value, 
			parseInt( raceLinesDiv.getAttribute(&apos;data-width&apos;) ) || plotFallbackWidth,
			racerRangeInput.value
		),
		raceLinesDiv
	);	
} )

&lt;/script&gt;


	</content>
</entry>



<entry>
	<title>A House Full of Wizard-Shaped Holes</title>
	<link rel="alternate" href="https://ericportis.com/posts/2025/a-house-full-off-wizard-shaped-holes/" />
	<id>tag:ericportis.com,2025-02-28:/posts/2025/a-house-full-off-wizard-shaped-holes/</id>
	<updated>2025-02-28T00:00:00-08:00</updated>
	<content type="html">
	    &lt;style&gt;
.photo-group,
article &gt; figure.photo-group {
	display: flex;
	overflow: scroll;
	scroll-snap-type: x mandatory;
	gap: 1em;
	padding-inline-start: 50vw;
	padding-inline-end: 50vw;
	height: 80vh;
	max-width: none !important;
	margin-block-start: 2rem;
	margin-block-end: 2rem;
	margin-left: 0 !important;
	margin-right: 0 !important;
}
.photo-group,
article &gt; figure.photo-group:has( figcaption ) {
	height: calc( 80vh + 1em );
}
.photo-group &gt; figure {
	margin: 0;
	padding: 0;
	height: 100%;
	--figcaption-height: 2lh;
}
@media (min-width: 480px) {
	.photo-group &gt; figure {
		--figcaption-height: 1lh;
	}
}
.photo-group figcaption {
	height: var(--figcaption-height);
	margin-block-start: 1em;
	margin-block-end: 0;
}

.photo-group img,
.photo-group img[width][height] {
	height: 100%;
	width: auto;
	max-width: 80vw;
	object-fit: contain;
	object-position: center center;
	margin: 0;
	padding: 0;
	scroll-snap-align: center;
	scroll-snap-stop: always; /* only ever advance one at a time */
}
.photo-group &gt; figure img,
.photo-group &gt; figure img[width][height] {
	height: calc( 100% - var(--figcaption-height) - 1em );
	/* captions can only be two lines?! */
	/* make this solution... better someday */
}
&lt;/style&gt;

&lt;p&gt;
We adopted Wizard on March 12th, 2016 and we said goodbye to him on January 13th, 2025.
&lt;/p&gt;

&lt;div class=&quot;photo-group&quot;&gt;
&lt;!--
&lt;img src=&quot;https://o.img.rodeo/b_papayawhip/h_1000,ar_5:7/l_text:Roboto_30:gotcha%20pic/fl_layer_apply/_.png&quot;&gt;
--&gt;
&lt;figure&gt;
&lt;img id=&apos;IMG_9969&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_9969.jpg&apos; 
     width=&apos;640&apos; 
     height=&apos;640&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_9969.jpg 640w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_9969/half.jpg 320w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_9969/quarter.jpg 160w&apos;
      sizes=&quot;calc(80vh * (640/640))&quot; width=&quot;640&quot; height=&quot;640&quot; alt=&quot;My partner and I crouching in the grass in front of a green house, with big goofy smiles on our faces. A big brindle hound dog (Wizard) with his ears all the way up and just the tip of his tounge out stands between us and leans forward toward the photographer while we hold on to his hips.&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;2016 (Gotcha Day)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;img id=&apos;IMG_7324&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7324/quarter.jpg&apos; 
     width=&apos;2448&apos; 
     height=&apos;3264&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7324/quarter.jpg 612w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7324.jpg 2448w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7324/half.jpg 1224w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7324/8th.jpg 306w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7324/16th.jpg 153w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (2448/3264))&quot; width=&quot;2448&quot; height=&quot;3264&quot; alt=&quot;Wizard laying in the grass, with his paw on my lap. I&apos;ve got my hand on his paw. He&apos;s resting his head on his arm but looking up at me sideways in a coy way.&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;2016&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;img id=&apos;IMG_6322&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_6322/quarter.jpg&apos; 
     width=&apos;3264&apos; 
     height=&apos;2448&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_6322/quarter.jpg 816w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_6322.jpg 3264w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_6322/half.jpg 1632w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_6322/8th.jpg 408w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_6322/16th.jpg 204w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (3264/2448))&quot; width=&quot;3264&quot; height=&quot;2448&quot; alt=&quot;Overhead view of my partner sitting on the couch with a laptop and a cup of coffee; Wizard is laying next to her on his back, snuggled close, both front legs stretched up straight so that his paws are by her shoulder. He&apos;s sleeping with a huge smile on his face. There&apos;s a little dog toy next to him.&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;2016&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;img id=&apos;IMG_7835&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7835/quarter.jpg&apos; 
     width=&apos;3024&apos; 
     height=&apos;4032&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7835/quarter.jpg 756w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7835.jpg 3024w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7835/half.jpg 1512w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7835/8th.jpg 378w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/early-and-late/IMG_7835/16th.jpg 189w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (3024/4032))&quot; width=&quot;3024&quot; height=&quot;4032&quot; alt=&quot;A white-faced Wizard curled up in a dog bed with his head low, looking at the camera (very cute face).&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;2025&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;
&lt;a href=&quot;https://alternativehumanesociety.com&quot;&gt;They&lt;/a&gt; said he was six years old when we got him, but I don’t believe it. He weighed about 75 lbs and he taught us a lot about love. He was our first dog, but we were not his first people. We all figured it out, eventually.
&lt;/p&gt;

&lt;div class=&quot;photo-group&quot;&gt;
&lt;figure&gt;
&lt;img id=&apos;IMG_8310&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_8310/quarter.jpg&apos; 
     width=&apos;2448&apos; 
     height=&apos;3264&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_8310/quarter.jpg 612w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_8310.jpg 2448w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_8310/half.jpg 1224w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_8310/8th.jpg 306w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_8310/16th.jpg 153w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (2448/3264))&quot; width=&quot;2448&quot; height=&quot;3264&quot; alt=&quot;Photograph of a folded newspaper, held up in front of a cute/innocent-looking Wizard. The relevant entry in the police blotter reads, “A visitor to Orcas Island was bitten by a dog at the dog park. The dog was current on its vaccinations, and no serious injury occurred. The visitor requested no further investigation and just wanted the incident documented.”&quot; /&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img id=&apos;IMG_0386&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_0386/half.jpg&apos; 
     width=&apos;1484&apos; 
     height=&apos;770&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_0386/half.jpg 742w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_0386.jpg 1484w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_0386/quarter.jpg 371w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/trouble/IMG_0386/8th.jpg 186w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (1502/756))&quot; width=&quot;1502&quot; height=&quot;756&quot; alt=&quot;Scan of a printed “run card,” from a kennel. It reads, “Wizard - neutered male 9 years 5 mos Brindle. Behavior/health issues: Destroys kennels/runs.” There&apos;s also a sideways profile photo of Wizard looking real cute on a dog bed in front of a chain-link fence.&quot; /&gt;

&lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;
It became a thing that old men, passing us in public, would express a particular affinity for Wizard. “That’s a good-lookin’ dog!” they’d say. I told kids he was half-dog, half-tiger.
&lt;/p&gt;

&lt;div class=&quot;photo-group&quot;&gt;

&lt;figure&gt;
	&lt;img id=&apos;IMG_4788&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_4788/quarter.jpg&apos; 
     width=&apos;2399&apos; 
     height=&apos;3000&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_4788/quarter.jpg 600w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_4788.jpg 2399w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_4788/half.jpg 1200w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_4788/8th.jpg 300w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_4788/16th.jpg 150w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (2399/3000))&quot; width=&quot;2399&quot; height=&quot;3000&quot; alt=&quot;A cheesy/ironic photo-illustration with both a floating bust and full-body portrait of Wizard, photoshopped into an idyllic mountain/lake landscape. The floating bust shows Wizard smiling with his tounge out; in the full-body portrait he he is seated and looking sweet but also like he wants something.&quot; /&gt;

	&lt;figcaption class=&quot;info-line center&quot;&gt;
		Portrait by &lt;a href=&quot;https://www.instagram.com/wildmasterpieces/&quot;&gt;Evan Douglas (aka Wild Masterpieces)&lt;/a&gt;
	&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
	&lt;img id=&apos;623&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/623.jpg&apos; 
     width=&apos;900&apos; 
     height=&apos;900&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/623.jpg 900w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/623/half.jpg 450w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/623/quarter.jpg 225w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (900/900))&quot; width=&quot;900&quot; height=&quot;900&quot; alt=&quot;An illustration of Wizard, dressed as Harry Potter, wearing a scarf, circle-frame glasses, a robe, and a yellow and red scarf and tie. He&apos;s holding a wand in his mouth, and a white owl is hanging out behind him.&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;
Portrait by &lt;a href=&quot;https://eiffel.art&quot;&gt;Matt Cummings&lt;/a&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
	&lt;img id=&apos;IMG_6937&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_6937/half.jpg&apos; 
     width=&apos;1548&apos; 
     height=&apos;1548&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_6937/half.jpg 774w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_6937.jpg 1548w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_6937/quarter.jpg 387w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_6937/8th.jpg 194w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (1548/1548))&quot; width=&quot;1548&quot; height=&quot;1548&quot; alt=&quot;An illustration of our family, with busts of my wife, myself, and Wizard in the middle. It&apos;s cartoon-ish but the likenesses are great.&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;
Family portrait by &lt;a href=&quot;https://www.instagram.com/fuzzzbrain/&quot;&gt;Hannah B. (aka fuzzzbrain)&lt;/a&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
	&lt;!-- no srcset needed --&gt;
	&lt;img
		src=&quot;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/kidpix-portrait.png&quot;
		loading=&quot;lazy&quot;
		width=&quot;512&quot;
		height=&quot;512&quot;
		alt=&quot;A pixellated illustration of Wizard&apos;s head poking through the hole of a giant dount, drawn using KidPix. Lots of noise and texture and color.&quot;
		style=&quot;image-rendering: pixelated;&quot;
	/&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;
KidPix portrait by &lt;a href=&quot;https://eccentricflow.com/&quot;&gt;Trish Reynolds&lt;/a&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
	&lt;img id=&apos;IMG_8340&apos; 
     src=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_8340/quarter.jpg&apos; 
     width=&apos;3024&apos; 
     height=&apos;4032&apos; 
     srcset=&apos;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_8340/quarter.jpg 756w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_8340.jpg 3024w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_8340/half.jpg 1512w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_8340/8th.jpg 378w, /assets/2025-02-28-a-house-full-of-wizard-shaped-holes/art/IMG_8340/16th.jpg 189w&apos;
      loading=&quot;lazy&quot; sizes=&quot;auto, calc(80vh * (3024/4032))&quot; width=&quot;3024&quot; height=&quot;4032&quot; alt=&quot;A photo of a sock, worn with some leather shoes and rolled-up jeans. The sock has an illustration of Wizard printed on it. He&apos;s staring straight ahead and looks quite serious.&quot; /&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;
Sock portrait by unknown sock artist
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;
He hated getting wet and he loved the sun. All dogs seem pretty food-motivated but he was, like, &lt;em&gt;very&lt;/em&gt; food-motivated? The first time we gave him turkey he started humping. He was curious, coy, sleepy, and sweet. He’d kerchuck-kerplunk down the stairs forty-five minutes after I got up, find me in the kitchen, and lean into me with his warmth and his weight. Good morning! More than anything, he hated being alone. So we made sure he wasn’t, for nine years.
&lt;/p&gt;

&lt;!--
&lt;p&gt;
When we got him, he had a lot of bad dreams. As the years rolled by, there was less sleep-growling and sleep-woofing and more just-sleeping. And even some sleep-tail-wagging, from time to time.
&lt;/p&gt;
--&gt;

&lt;p&gt;
We loved him so much, and he loved us right back. It was all so automatic.
&lt;/p&gt;

&lt;!--
One thing I didn&apos;t understand was how much everyone around us knew this. Since he passed we&apos;ve received flowers and cards – two framed portraits. I didn&apos;t think it would help but it helps. The kennel – the same one that left that little note up above – gave us some wind chimes.
--&gt;

&lt;p&gt;
And now he’s gone and our house is too big and lonely. It’s full of holes: where his beds used to be, where his stuff used to be, where he used to be. Holes in our hearts.
&lt;/p&gt;

&lt;p&gt;
Buddy. Wizzy. Boo! Wizard. Thank you.
&lt;/p&gt;

&lt;video controls width=1080 height=1920 style=&quot;max-height: 80vh;&quot;
	poster=&quot;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/IMG_5865/poster.jpg&quot;
	aria-label=&quot;Wizard laying down, at sunset, on top of a small grassy mountain-top. The video is taken from behind. He&apos;s looking down at rolling forests stretching out to the ocean. The ocean dotted with islands; in the distant background, there is a mountain range. He&apos;s lazily chewing a stick.&apos;&quot;
&gt;
	&lt;source
		media=&quot;
			((min-resolution: 1.5dppx) and (min-height: 768px)) or
			(min-height: 1536px)
		&quot;
		src=&quot;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/IMG_5865.mp4&quot;
	&gt;
	&lt;source
		media=&quot;
			((min-resolution: 1.5dppx) and (min-height: 512px)) or
			(min-height: 1024px)
		&quot;
		src=&quot;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/IMG_5865/1280.mp4&quot;
	&gt;
	&lt;source
		media=&quot;
			((min-resolution: 1.5dppx) and (min-height: 384px)) or
			(min-height: 768px)
		&quot;
		src=&quot;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/IMG_5865/960.mp4&quot;
	&gt;
	&lt;source
		src=&quot;/assets/2025-02-28-a-house-full-of-wizard-shaped-holes/IMG_5865/640.mp4&quot;
	&gt;
&lt;/video&gt;


	</content>
</entry>



<entry>
	<title>Fixing my Sony WH-1000XM3s by sticking some sponge in there</title>
	<link rel="alternate" href="https://ericportis.com/posts/2024/fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/" />
	<id>tag:ericportis.com,2024-09-04:/posts/2024/fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/</id>
	<updated>2024-09-04T00:00:00-07:00</updated>
	<content type="html">
	    &lt;p&gt;Five years ago, I bought a pair of &lt;a href=&quot;https://www.rtings.com/headphones/reviews/sony/wh-1000xm3-wireless&quot;&gt;Sony WH-1000XM3s&lt;/a&gt;. They’re great!&lt;/p&gt;

&lt;p&gt;Three years ago, they stopped turning on consistently. The problem got worse and worse. I Googled. I Reddit-ed. I YouTube’d. I – carefully, gently – learned how to open them up. I developed a series of incorrect hypotheses about possible problems and solutions, the funniest of which involved tapping a series of internal solder points with the tip of my screwdriver in order to, I don’t know, release some excess charge or something? It seemed to work! (For a while). When I began flying again (remember?) I packed a little screwdriver in my carry-on, in case the ol’ headphones needed a good solder-tapping during a flight. TSA only caught it once.&lt;/p&gt;

&lt;p&gt;All of my “solutions” worked just well enough to string me along. But steadily, inevitably, they lost effectiveness. I almost gave up! Until I stuck some sponge in there.&lt;/p&gt;

&lt;p&gt;These things have taken a dozen drops from head-height onto hard floors. Perhaps the sponge is applying pressure to some impact-loosened contact, allowing the electrons to flow? I don’t know.&lt;/p&gt;

&lt;p&gt;What I do know is: now, my headphones turn on every time, for months at a time, and when they do occasionally start to fade on me (once a year or so?), I stick some &lt;em&gt;more&lt;/em&gt; sponge in there, and they are &lt;em&gt;revived.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last time I performed this “repair,” I remembered to take some pictures.&lt;/p&gt;

&lt;p&gt;First, we gather our materials.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5335&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5335/quarter.jpg&quot; width=&quot;3528&quot; height=&quot;2646&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5335/quarter.jpg 882w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5335.jpg 3528w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5335/half.jpg 1764w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5335/8th.jpg 441w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5335/16th.jpg 221w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;From left to right: a precision screwdriver, a pair of Sony WH-1000XM3 headphones, some If You Care sponge cloths (very thin sheets of spongy material), and a pair of scissors.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We’re going to start by going in through the front. Pry the ear pad off of the left headphone. I remember this being difficult and nerve-wracking the first time I did it, but I must have worn the plastic bits down by doing it so often over the years – now it’s easy.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5336&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5336/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5336/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5336.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5336/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5336/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5336/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Close up on the left headphone, laying driver-up. My hand is prying the pad off.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5337&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5337/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5337/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5337.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5337/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5337/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5337/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Hand holding up the pried-off pad, showing all of the plastic tabs that hold it in place.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Remove foam.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5338&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5338/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5338/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5338.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5338/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5338/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5338/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Hand holding up an easily-removed ovular foam pad.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s what the inside looks like. We’re gonna remove the four circled screws.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5339&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5339/quarter.jpg&quot; width=&quot;3024&quot; height=&quot;4032&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5339/quarter.jpg 756w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5339.jpg 3024w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5339/half.jpg 1512w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5339/8th.jpg 378w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5339/16th.jpg 189w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;The inside of the headphone. There is a big driver in the middle, surrounded by complex plastic shapes and a number of Phillips-head screws. The four screws that are closest to the driver, making a rectangle around it, are circled in yellow.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I asked my parents for this set of Wiha precision screwdrivers for my ~17th birthday. I distinctly remember my dad not-quite-derisively asking me what I thought I was going to use them for. Well!&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5340&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5340/quarter.jpg&quot; width=&quot;3329&quot; height=&quot;2497&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5340/quarter.jpg 832w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5340.jpg 3329w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5340/half.jpg 1665w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5340/8th.jpg 416w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5340/16th.jpg 208w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Hand holding up the handle of a narrow, small, blank Wiha precision screwdriver, with a red butt. It has some text in white which reads, “261 / PH0 x 50; Made in Germany,” alongside the Wiha logo.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, we open up the back. Carefully flip the headphones over and carefully lift the back shell off, using the ribbon cable that runs between outer/right side the shell and the main body of the headphone as a hinge.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5341&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5341/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5341/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5341.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5341/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5341/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5341/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Headphones are now facing driver-down. Hand is starting to lift the back off of the left headphone, revealing some gray plastic, wires, the tip of a circuitboard, and – what’s that? — some pre-existing sponge!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5342&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5342/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5342/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5342.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5342/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5342/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5342/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;The back is now hinged off, to almost 90°. There are two pieces of existing sponge lying on top of a circuitboard.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Stick some sponge in there!&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5343&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5343/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5343/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5343.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5343/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5343/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5343/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Hand adding a square piece of sponge sheet. There are now three bits of sponge in there.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Reassemble. All better!&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;IMG_5352&quot; src=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5352/quarter.jpg&quot; width=&quot;4032&quot; height=&quot;3024&quot; srcset=&quot;/assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5352/quarter.jpg 1008w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5352.jpg 4032w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5352/half.jpg 2016w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5352/8th.jpg 504w, /assets/2024-09-04-fixing-my-sony-wh-1000xm3s-by-sticking-some-in-there/IMG_5352/16th.jpg 252w&quot; loading=&quot;lazy&quot; sizes=&quot;auto&quot; data-scroll-target=&quot;&quot; alt=&quot;Reassembled headphones lying on the table. Their blue LED power indicator is on. Hand is giving a big thumbs-up.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now that I’m &lt;a href=&quot;https://www.reddit.com/r/sony/comments/mq36xy/fixed_sony_wh1000xm3_not_turning_on_or/&quot;&gt;Redditing this again&lt;/a&gt;, it looks like other folks have also discovered this solution. Cool! All remaining WH-1000XM3 owners should know! Maybe try sticking some sponge in there? Worked for me!&lt;/p&gt;

	</content>
</entry>



<entry>
	<title>Endless Shrimp Jesus</title>
	<link rel="alternate" href="https://ericportis.com/posts/2024/endless-shrimp-jesus/" />
	<id>tag:ericportis.com,2024-06-03:/posts/2024/endless-shrimp-jesus/</id>
	<updated>2024-06-03T00:00:00-07:00</updated>
	<content type="html">
	    &lt;p&gt;This post is about a cynical analogy for understanding the generative AI boom. The analogy is not perfect! It minimizes the (&lt;a href=&quot;https://simonwillison.net/2024/Mar/22/claude-and-chatgpt-case-study/#llms-are-useful&quot;&gt;real&lt;/a&gt;, &lt;a href=&quot;https://www.citationneeded.news/ai-isnt-useless/&quot;&gt;even to skeptics&lt;/a&gt;) present utility of generative AI, and completely discounts the technology’s hypothesized future benefits. But, as I try to process what in the heck is happening in tech right now, this analogy has been helpful for me. Maybe it’ll help you, too.&lt;/p&gt;

&lt;picture&gt;
&lt;source format=&quot;image/avif&quot; srcset=&quot;/assets/2024-06-03-endless-shrimp-jesus/shrimp-jesus.jpg&quot; /&gt;
&lt;img class=&quot;p-width&quot; src=&quot;/assets/2024-06-03-endless-shrimp-jesus/shrimp-jesus.jpg&quot; alt=&quot;An AI-generated full-body portrait of Jesus, except he is standing under water and is made of hundreds (thousands?) of shrimp.&quot; /&gt;
&lt;/picture&gt;

&lt;p&gt;Red Lobster just went bankrupt. A particularly funny (if &lt;a href=&quot;https://prospect.org/economy/2024-05-22-raiding-red-lobster/&quot;&gt;ultimately small&lt;/a&gt;) part of Red Lobster’s financial decline was that, over the past year, the company lost tens of millions of dollars selling Endless Shrimp.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.seafoodnews.com/Story/1255743/Endless-Shrimp-Promotion-is-Back-at-Red-Lobster-All-Day-Every-Day&quot;&gt;Endless Shrimp promotion&lt;/a&gt;, which let patrons exchange $20 for (theoretically) infinite shrimp, has since come under scrutiny. Why would a failing business try to sell as many money-losing shrimp as possible? Perhaps as a loss leader, to get folks in the door, so that Red Lobster could try to make money off of them in other ways? That’s the boring explanation.&lt;/p&gt;

&lt;p&gt;Here’s the exciting one: Endless Shrimp was launched last June because Red Lobster was, at that point, &lt;em&gt;owned by its shrimp supplier&lt;/em&gt; (Thai Union). Thai Union saw the writing on the wall — Red Lobster was in a tailspin — and wanted to squeeze as much money as they could out of a bad investment. How? Get zombie-Red-Lobster to spend as many of its remaining dollars as possible on Thai Union’s shrimp.&lt;/p&gt;

&lt;p&gt;I learned about all of this from Matt Levine. In &lt;a href=&quot;https://www.bloomberg.com/news/audio/2024-05-24/money-stuff-podcast-bad-monday-shrimp-fingers-brains&quot;&gt;a recent episode of the Money Stuff podcast&lt;/a&gt;, Matt drew on &lt;a href=&quot;https://apoorv03.com/p/mang&quot;&gt;some analysis from Apoorv Agrawal&lt;/a&gt; to make an analogy.&lt;/p&gt;

&lt;p&gt;The essential conflict of interest here – one company both owning &lt;em&gt;and&lt;/em&gt; supplying another – is also pervasive in the AI industry. AI company valuations have been driven by enormous investments from cloud computing companies. Those deals give the AI companies tons of cash, which they &lt;a href=&quot;https://apoorv03.com/i/140792488/the-supply-side-digital-tollbooths-printing-cash&quot;&gt;turn around and hand right back to their investors&lt;/a&gt; in exchange for compute.&lt;/p&gt;

&lt;p&gt;So in this analogy:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OpenAI/Anthropic/Databricks = Red Lobster&lt;/li&gt;
  &lt;li&gt;Microsoft/Amazon/NVIDIA = Thai Union&lt;/li&gt;
  &lt;li&gt;GPUs = raw shrimp&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://geoffgraham.me/struggling-with-ai-iconography-for-ui-design/&quot;&gt;✨ buttons&lt;/a&gt; = Endless Shrimp (eat up!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, this is not a perfect analogy. I don’t think that Microsoft thinks OpenAI is a zombie – worth $0 – which they must extract as much value as possible from, before an inevitable bankruptcy; I think they think generative AI is going to unlock wildly valuable new ways of computing and that their investment in OpenAI is going to pay off big-time.&lt;/p&gt;

&lt;p&gt;And yet, this idea does help me make sense of a couple of things.&lt;/p&gt;

&lt;p&gt;First: how on earth AI companies are flying &lt;em&gt;so&lt;/em&gt; high, when the rest of tech is struggling in a “high-interest-rate environment.” It’s not just that the folks who still have money to spare think AI is the next big thing. It’s also that it’s much easier to invest a couple billion in something when you know you’re going to get a bunch of it right back.&lt;/p&gt;

&lt;p&gt;Second: the feeling I have, as a user of technology, that I am being offered more, and more, and more AI stuff (shrimp). Shrimp at all costs; shrimp beyond utility, or reason. &lt;a href=&quot;https://finance.yahoo.com/news/cost-training-ai-could-soon-101348308.html&quot;&gt;Don’t these shrimp cost money?&lt;/a&gt; &lt;a href=&quot;https://www.theregister.com/2024/04/03/stability_ai_bills/&quot;&gt;How can they afford all of these shrimp?&lt;/a&gt; No matter; cloud compute companies are slamming their collective feet on their own growth accelerators. The shrimp must flow.&lt;/p&gt;

&lt;p&gt;Through this lens, the reason that generative AI is booming is not that it produces value for customers, but rather that it consumes a lot of what the folks with money to spare are selling. This is a cynical and incomplete model for what’s happening; it doesn’t explain everything. But it has helped me see some things that I was missing.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.404media.co/email/1cdf7620-2e2f-4450-9cd9-e041f4f0c27f/&quot;&gt;Amen 🙏🏻🙏🏻🙏🏻&lt;/a&gt;.&lt;/p&gt;

	</content>
</entry>



<entry>
	<title>Okay, Color Spaces</title>
	<link rel="alternate" href="https://ericportis.com/posts/2024/okay-color-spaces/" />
	<id>tag:ericportis.com,2024-02-20:/posts/2024/okay-color-spaces/</id>
	<updated>2024-02-20T00:00:00-08:00</updated>
	<content type="html">
	    &lt;style&gt;
.color {
	border: 1px solid hsl(0deg 0% 58% / 0.36);
	word-break: no-break;
	/* background-color: #eee; */
	padding: 0.0ch 0.5ch 0.125ch 0.5ch;
	border-radius: 0.25em;
	white-space: nowrap;
}
.swatch {
	display: inline-block;
	height: 0.8625em;
	width: 0.8625em;
	border-radius: 2px;
	border: 1px solid hsl(0deg 0% 58% / 0.36);
	margin-right: 0.75ch;
	position: relative;
	top: 0.1em;
	box-shadow:
	    0px 0.6px 0.7px hsl(0deg 0% 58% / 0.36),
    	0.1px 1.9px 2.1px -0.8px hsl(0deg 0% 58% / 0.36),
	    0.1px 4.9px 5.5px -1.7px hsl(0deg 0% 58% / 0.36),
	    0.3px 11.8px 13.3px -2.5px hsl(0deg 0% 58% / 0.36);
}


.surfaceAndCrossectionContainer {
	display: grid;
	grid-template-columns: 1fr 1fr;
}
@media (max-width: 400px) {
	.surfaceAndCrossectionContainer {
		grid-template-columns: 1fr;
	}
}
canvas {
	width: 100%;
}
.crosssectionContainer {
	display: grid;
	position: relative;
	top: -7.5%;
	pointer-events: none; /* daniel holbert fix! */
}
.crosssectionContainer &gt; * {
	box-sizing: border-box;
	grid-area: 1/1;
	width: 100%;
	margin: 0;
}
.crosssectionContainer &gt; canvas {
	/* get data to line up with grid/axes */
	padding: 13.333%; /* 20px / 150px */
	
	/* other stuff */
	image-rendering: pixelated;
	/* outline: 1px solid rgb( 10% 10% 10% / 0.25 ); */
}
#xyz-crosssection {
	transform: rotate(270deg);
}
#oklab-crosssection {
	transform: scaleY(-1);
}
.inputContainer {
	padding: 0.5em;
	background: white;
	border-radius: 0.25em;
	width: max-content;
	margin: 1em auto;
}
.inputContainer input {
	width: 18em;
	height: 1em;
}
.inputContainer output {
	display: inline-block;
	min-width: 4ch;
	margin-left: 0.5ch;
}
label {
	display: block;
}
output {
	font-family: Monaco, monospaced;
	width: 5ch;
	overflow: clip;
	font-size: 0.8em;
}
.color-solid {
	cursor: grab;
}
.info-line math {
	font-size: 1.125em;
}
.info-line {
	text-wrap: balance;
}
figcaption.info-line p {
	padding-left: 1em;
	padding-right: 1em;
}

aside.footnotes {
	font-size: 0.8625em;
	background: #ddd;
	padding: 1em;
	border-radius: 1em;
}

&lt;/style&gt;


&lt;h2 class=&quot;little-h&quot;&gt;Colors… &lt;em&gt;in… spaaaaaaace&lt;/em&gt;&lt;/h2&gt;

&lt;p&gt;
What is a “color space?”

&lt;p&gt;
Well first you take some &lt;em&gt;colors.&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: red;&quot;&gt;&lt;/span&gt;red&lt;/code&gt;
&lt;li&gt;&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: yellow;&quot;&gt;&lt;/span&gt;yellow&lt;/code&gt;
&lt;li&gt;&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: blue;&quot;&gt;&lt;/span&gt;blue&lt;/code&gt;
&lt;/ul&gt;

&lt;p&gt;
And then you arrange them, however you like, into some kind of &lt;em&gt;space:&lt;/em&gt;

&lt;figure&gt;
&lt;img
	alt=&quot;A Cartesian plane with a horizontal X axis and a vertical Y axis. Three square color swatches are arranged in a triangle around the origin; red at the top, yellow in the bottom left, and blue in the bottom right.&quot;
	style=&quot;width: 75%;&quot;
	src=&quot;/assets/2024-02-20-okay-color-spaces/toy-colorspace2.svg&quot;
/&gt;
&lt;/figure&gt;

&lt;p&gt;
The space has &lt;em&gt;coordinates&lt;/em&gt;. In the little toy color space we&apos;ve just defined, &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: red;&quot;&gt;&lt;/span&gt;red&lt;/code&gt; is at &lt;math&gt;&lt;mo&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;)&lt;/mo&gt;&lt;/math&gt;, &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: yellow;&quot;&gt;&lt;/span&gt;yellow&lt;/code&gt; is at &lt;math&gt;&lt;mo&gt;(&lt;/mo&gt;&lt;mn&gt;-1&lt;/mn&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;mn&gt;-1&lt;/mn&gt; &lt;mo&gt;)&lt;/mo&gt;&lt;/math&gt;, and &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: blue;&quot;&gt;&lt;/span&gt;blue&lt;/code&gt; is at &lt;math&gt;&lt;mo&gt;(&lt;/mo&gt; &lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;,&lt;/mo&gt; &lt;mn&gt;-1&lt;/mn&gt; &lt;mo&gt;)&lt;/mo&gt;&lt;/math&gt;. That’s a color space!

&lt;p&gt;
You might ask: what kind of color might we find at the origin: &lt;math&gt;&lt;mo&gt;(&lt;/mo&gt; &lt;mn&gt;0&lt;/mn&gt;&lt;mo&gt;,&lt;/mo&gt; &lt;mn&gt;0&lt;/mn&gt; &lt;mo&gt;)&lt;/mo&gt;&lt;/math&gt;? Right now, it’s undefined, but we could put anything there: &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: black;&quot;&gt;&lt;/span&gt;black&lt;/code&gt;, &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: gray;&quot;&gt;&lt;/span&gt;gray&lt;/code&gt;, &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: brown;&quot;&gt;&lt;/span&gt;brown&lt;/code&gt;, heck – even &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: hotpink;&quot;&gt;&lt;/span&gt;hotpink&lt;/code&gt; if we wanted.

&lt;p&gt;
Which brings us to the first point: color spaces are all constructs. People just make them up! Useful ones are constructed in order to do useful things, but there’s no, like, One True Fundamental Color Space.&lt;/p&gt;

&lt;figure&gt;
&lt;img
	alt=&quot;A donut-shape divided into six segments. Three of them are slightly larger: red at the top, blue in the bottom right, and yellow in the bottom left. The in-between segments are smaller: purple lies between red and blue, green between blue and yellow, and orange between yellow and red.&quot;
	src=&quot;/assets/2024-02-20-okay-color-spaces/RYB_color_wheel.svg&quot;
	style=&quot;width: 50%;&quot;
/&gt;
&lt;figcaption class=&quot;info-line center&quot; style=&quot;line-height: 1.2;&quot;&gt;The &lt;a href=&quot;http://www.huevaluechroma.com/072.php&quot;&gt;red-yellow-blue color wheel&lt;/a&gt;&lt;br/&gt;(as handed down from &lt;a href=&quot;https://web.archive.org/web/20240226182459/http://www.colorsystem.com/?page_id=683&amp;lang=en&quot;&gt;Newton&lt;/a&gt; and &lt;a href=&quot;https://web.archive.org/web/20240226182506/http://www.colorsystem.com/?page_id=766&amp;lang=en&quot;&gt;Goethe&lt;/a&gt;).
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
When I was a kid, various art teachers taught me about The Color Wheel with its Three Primary Colors and its Three Secondary Colors and while that wheel did help me make greenish paint when the classroom only had yellow and blue, it &lt;em&gt;also&lt;/em&gt; gave me some wrong ideas about color.

&lt;p&gt;
You see, just like the &lt;a href=&quot;https://en.wikipedia.org/wiki/Food_pyramid_(nutrition)&quot;&gt;food pyramid&lt;/a&gt;, the RYB color wheel is &lt;em&gt;made up&lt;/em&gt;. It’s a conceptual model, invented by people, who invented it by arranging a set of things into an abstract, hierarchical geometry, as a problem-solving tool. The RYB color wheel was developed in order to give artists and eight-year-olds a (rough) way to (loosely) predict what might happen when they mixed certain pigments. But colors – like tastes, touches, and smells – don’t have any kind of innate geometric relationship to each other. When we arrange colors around a wheel, or set them into any other &lt;em&gt;space:&lt;/em&gt; we did that.

&lt;p&gt;
All of this is to say: color spaces can’t really be “right” (or “wrong.”) They can only be &lt;em&gt;useful.&lt;/em&gt;

&lt;h2 class=&quot;little-h&quot;&gt;CIE XYZ: a very useful color space&lt;/h2&gt;

&lt;p&gt;
The toy color space that I constructed at the beginning of this post is useless. It doesn&apos;t help us do anything.

&lt;p&gt;
What might a useful color space do? Well, just like the RYB color wheel, maybe it could help us predict how different colors will mix.

&lt;p&gt;
And lo, in 1931, &lt;a href=&quot;https://files.cie.co.at/529.pdf#page=22&quot;&gt;an international team of experts got together in England&lt;/a&gt; and laid out a color space that does exactly that&lt;sup id=&quot;fn-1-mark&quot;&gt;&lt;a href=&quot;#fn-1-note&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. They used &lt;a href=&quot;https://yuhaozhu.com/blog/cmf.html&quot;&gt;science and math&lt;/a&gt;. They called themselves the International Commission on Illumination (&lt;abbr class=&quot;small-caps&quot;&gt;aka&lt;/abbr&gt; &lt;i lang=&quot;fr&quot;&gt;le Commission Internationale de l&apos;Éclairage&lt;/i&gt;, &lt;abbr class=&quot;small-caps&quot;&gt;aka&lt;/abbr&gt; the &lt;abbr&gt;CIE&lt;/abbr&gt;), and they called their color space CIE XYZ.

&lt;p&gt;
Here are three colors plotted in CIE XYZ: a red, a green, and a blue.

&lt;!-- TODO width height aspect ratio --&gt;
&lt;figure&gt;
&lt;div
	class=&quot;primariesContainer&quot;
	id=&quot;xyz-primaries&quot;
	aira-role=&quot;img&quot;
	aria-label=&quot;A three dimensional chart, with X, Y, and Z axes, each going from the origin (zero) to one-point-zero. You are facing the origin, Z shoots towards you, past your left, X shoots towards you, past your right, and Y shoots straight up in the air. There are three swatches floating in the space. Blue hovers just off the floor to your left, almost directly on the Z axis, at almost Z=1. Red is a bit higher, and sits almost on top of the X axis, but only about half way down it (X=0.5). Finally, green is much higher - about three times as high as the red, and maybe two thirds of the way to the 1.0 mark on the Y axis. It appears, from your vantage point, slightly to the right of the Y axis, at around the quarter mark on the X axis.&quot;
&gt;
&lt;/div&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;Click/touch and drag to rotate the space&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
The first thing you might notice is, it&apos;s 3D! Turns out, most useful color spaces have at least three dimensions, although in order to visualize various things, people will often flatten 3D spaces into 2D.

&lt;p&gt;
Now, CIE XYZ isn’t limited to three colors. In fact, it contains &lt;em&gt;all&lt;/em&gt; of the colors&lt;sup id=&quot;fn-2-mark&quot;&gt;&lt;a href=&quot;#fn-2-note&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.



&lt;p&gt;
So let’s fill our visualization out a bit. The red, green, and blue that that I plotted above happen to be the three &lt;a href=&quot;https://en.wikipedia.org/wiki/Primary_color&quot;&gt;primary colors&lt;/a&gt; that define the &lt;a href=&quot;https://en.wikipedia.org/wiki/DCI-P3&quot;&gt;P3 color gamut&lt;/a&gt;. Here’s the rest of the P3 gamut, plotted in CIE XYZ:

&lt;figure class=&quot;wide&quot;&gt;
&lt;div class=&quot;inputContainer&quot;&gt;
	&lt;label class=&quot;info-line&quot; for=y&gt;Cross section &lt;math&gt;&lt;mi&gt;Y&lt;/mi&gt;&lt;/math&gt;&lt;/label&gt;
	&lt;input id=y type=&quot;range&quot; min=0 max=1 step=0.01 value=0.5&gt;
	&lt;output for=y&gt;&lt;/output&gt;
&lt;/div&gt;

&lt;div class=&quot;surfaceAndCrossectionContainer&quot;&gt;
	&lt;div
		class=&quot;surfaceContainer&quot;
		id=&quot;xyz-solid&quot;
		aria-role=&quot;img&quot;
		arial-label=&quot;A very colorful solid shape, with six sides, in our 3-D space with the same axes as before. The shape is like a squished cube that’s been skewed up and rotated towards us, or a parallelogram extruded thrugh space. Black at the point at the origin and white at the highest point, farthest from the origin. The red, blue, and green points are where the swatches were in the previous graphic. In addition to those four, and black and white, there are cyan, magenta, and yellow points at the other points on the smushed cube. Every face is a blend of the four colors at its corners. Much wider and taller than it is long.&quot;
	&gt;
	&lt;/div&gt;
	&lt;div class=crosssectionContainer
	     aria-role=&quot;img&quot;
	     aira-label=&quot;A chart with an X axis and a Z axis. Both go from the origin (0, 0) to 1. As the input slider above moves, we see a crossection of the solid at that Y value. Near Y=0, this shape is a dark triangle shooting quickly out along the Z axis, then it grows taller, getting red and magenta along its top side as its bottom becomes green, extending to a parallelogram. Then, for a while the whole parallelogram shifts up in the chart, not deforming or changing its fundamental shape (just moving), and gets lighter and lighter until about Y=0.7. Here, the bottom sides start retracting quickly, and it shrinks to a triangle with yellow on the left edge, very light teal on the right, getting whiter and whiter at the top. That triangle shrinks to a tiny white speck as we approach, and then reach, Y=1.&quot;
	&gt;
		&lt;canvas id=&quot;xyz-crosssection&quot;&gt;&lt;/canvas&gt;
		&lt;img src=&quot;./assets/crosssection-background.svg&quot; style=&quot;width: 100%;&quot;&gt;
	&lt;/div&gt;
&lt;/div&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Sweep the “cross section &lt;math&gt;&lt;mi&gt;Y&lt;/mi&gt;&lt;/math&gt;” slider to get a sense of how colors are distributed in the solid’s interior.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Unlike our toy color space, which defined three discrete colors, CIE XYZ is &lt;em&gt;continuous&lt;/em&gt;. I could pick any point in the CIE XYZ space and get a unique color.  That’s cool, but it’s still not useful. What’s &lt;em&gt;useful&lt;/em&gt; about CIE XYZ is the way it helps people solve the related problems of &lt;em&gt;predicting mixes&lt;/em&gt; and &lt;em&gt;creating matches&lt;/em&gt; of arbitrary colors.

&lt;p&gt;CIE XYZ is built out of &lt;a href=&quot;https://en.wikipedia.org/wiki/CIE_1931_color_space#Color_matching_functions&quot;&gt;three functions&lt;/a&gt;. Those functions take some wavelength of light as input, and give &lt;math&gt;&lt;mi&gt;X&lt;/mi&gt;&lt;/math&gt;, &lt;math&gt;&lt;mi&gt;Y&lt;/mi&gt;&lt;/math&gt;, and &lt;math&gt;&lt;mi&gt;Z&lt;/mi&gt;&lt;/math&gt; as output. This means you can sample some real-world light with a &lt;a href=&quot;https://en.wikipedia.org/wiki/Spectrometer&quot;&gt;spectrometer&lt;/a&gt;, do &lt;a href=&quot;https://en.wikipedia.org/wiki/CIE_1931_color_space#Computing_XYZ_from_spectral_data&quot;&gt;some math&lt;/a&gt;, and get an &lt;math&gt;&lt;mi&gt;X&lt;/mi&gt;&lt;/math&gt;, a &lt;math&gt;&lt;mi&gt;Y&lt;/mi&gt;&lt;/math&gt;, and a &lt;math&gt;&lt;mi&gt;Z&lt;/mi&gt;&lt;/math&gt;, which locate that light’s color within the CIE XYZ space.

&lt;p&gt;Let’s say I’ve got a pair of spotlights:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: #f2522b; background-color: color(xyz-d65 0.4 0.25 0.05);&quot;&gt;&lt;/span&gt;color(xyz-d65 0.40 0.25 0.05)&lt;/code&gt; (red)
&lt;li&gt;&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: #6dd327; background-color: color(xyz-d65 0.3 0.5 0.1);&quot;&gt;&lt;/span&gt;color(xyz-d65 0.30 0.50 0.10)&lt;/code&gt; (green)
&lt;/ul&gt;

&lt;p&gt;
What color will I see if I shine them both, full-blast, at the same spot? CIE XYZ lets me predict the result. The space has been structured in just such a way that all I have to do is add up the &lt;math&gt;&lt;mi&gt;X&lt;/mi&gt;&lt;/math&gt;s, &lt;math&gt;&lt;mi&gt;Y&lt;/mi&gt;&lt;/math&gt;s, and &lt;math&gt;&lt;mi&gt;Z&lt;/mi&gt;&lt;/math&gt;s:

&lt;p&gt;
&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: #ffdf3c; background-color: color(xyz-d65 0.7 0.75 0.15);&quot;&gt;&lt;/span&gt;color(xyz-d65 0.70 0.75 0.15)&lt;/code&gt; (yellow!)

&lt;p&gt;
By slicing a lil’ parallelogram out of the CIE XYZ space – with one corner at black, two corners at the locations of our two spotlights, and the last corner at the location of their full-blast mix – I can predict what &lt;em&gt;any&lt;/em&gt; combination of these two lights, at any intensity level, is going to look like.

&lt;script type=module&gt;
class lightSlider extends HTMLElement {
	connectedCallback() {
    const inputEl = this.querySelector(&apos;input&apos;);
    const outputEl = this.querySelector(&apos;output&apos;);
    const lightEl = document.querySelector(
      `svg #${
        this.getAttribute(&apos;target&apos;)
       }`
    );
    const otherLightSliderEl = document.querySelector(
      `#${
        this.getAttribute(&apos;other&apos;)
       }`
    );
    const intersectionEl = document.querySelector(
      `svg #${
        this.getAttribute(&apos;intersection&apos;)
       }`
    );
    
    const lightColor =
      lightEl.getAttribute(&apos;fill&apos;)
      .match(/color\(xyz-d65 ([\-\d\.]+) ([\-\d\.]+) ([\-\d\.]+)\)/)
      .slice(1)
      .map( s =&gt; parseFloat(s) );
    this.color = lightColor;
   
    inputEl.addEventListener(&apos;input&apos;, () =&gt; {
      outputEl.textContent = `${ Math.round( inputEl.value * 100 ) }%`;
      this.color = lightColor.map( n =&gt; n * inputEl.value );
      const newFill = `color(xyz-d65 ${ this.color[0] } ${ this.color[1] } ${ this.color[2] })`;
      lightEl.setAttribute( &apos;fill&apos;, newFill );
      const intersectionColor = this.color.map( ( n, i ) =&gt;
        n + otherLightSliderEl.color[i]
      );
      // console.log( intersectionColor )
      const newIntersectionFill = `color(xyz-d65 ${ intersectionColor[0] } ${ intersectionColor[1] } ${ intersectionColor[2] })`;
      intersectionEl.setAttribute( &apos;fill&apos;, newIntersectionFill )
    } );
	}
}

customElements.define(&quot;light-slider&quot;, lightSlider);
&lt;/script&gt;
&lt;style&gt;
svg.spotlights {
  background: black;
  width: 100%;
  border: 1em solid black;	
}
light-slider {
	display: block;
}
hr {
	border-style: solid;
	border-color: #eee;
}
&lt;/style&gt;
&lt;figure class=&quot;wide&quot;&gt;
  &lt;div class=&quot;inputContainer&quot;&gt;
  &lt;light-slider id=leftLightSlider target=left other=rightLightSlider intersection=intersection&gt;
    &lt;label class=&quot;info-line&quot; for=&quot;left_input&quot;&gt;Red spotlight intensity&lt;/label&gt;
    &lt;input id=left_input type=range min=0 max=1 value=1 step=.01&gt;
    &lt;output for=left_input&gt;100%&lt;/output&gt;
  &lt;/light-slider&gt;
  &lt;hr/&gt;
  &lt;light-slider id=rightLightSlider target=right other=leftLightSlider intersection=intersection&gt;
    &lt;label class=&quot;info-line&quot; for=&quot;right_input&quot;&gt;Green spotlight intensity&lt;/label&gt;
	&lt;input id=right_input type=range min=0 max=1 value=1 step=.01&gt;
    &lt;output for=right_input&gt;100%&lt;/output&gt;
  &lt;/light-slider&gt;
  
&lt;/div&gt;
&lt;div class=&quot;surfaceAndCrossectionContainer&quot;&gt;

&lt;!-- TODO can we switch this fill=&quot;color()&quot; to have fallbacks? like style=&quot;fill: #hex; fill: color();&quot; --&gt;
&lt;svg 
	class=&quot;spotlights&quot;
	viewbox=&quot;0 0 50 50&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Two overlapping circles on a black background. The left one is red and the right one is green. When they are both at full intensity, the overlapping section is yellow. If the green dims while the red is at full intensity, the middle section gets orange, and then red. As the red dimms while the green is at full intensity, the yellow just gets... greener and greener. When both are dim the middle section is very dark; when both are at 0% the entire image is solid black.&quot;
&gt;
  &lt;defs&gt;
    &lt;clipPath id=&quot;clip&quot;&gt;
      &lt;circle r=23.05 cx=23 cy=25 /&gt;
    &lt;/clipPath&gt;
  &lt;/defs&gt; 
  &lt;circle id=left r=23 cx=23 cy=25 fill=&quot;color(xyz-d65 0.40 0.25 0.05)&quot; /&gt;
  &lt;circle id=right r=23 cx=27 cy=25 fill=&quot;color(xyz-d65 0.30 0.50 0.10)&quot; /&gt;
  &lt;circle id=intersection r=23.05 cx=27 cy=25 clip-path=&quot;url(#clip)&quot; fill=&quot;color(xyz-d65 0.70 0.75 0.15)&quot; /&gt; 
&lt;/svg&gt;

&lt;div
	class=&quot;sliceContainer&quot;
	id=&quot;xyz-slice&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;The solid shape from before has become very transaprent, and there is an opaque parallelogram visible inside of it, extending up from the origin, vertically, close to parallel with the X axis. As the spotlight intensity sliders move and the spotlights in the adjacent image change color, a white circle on this parallelogram moves linearly, circling the color within the XYZ colorspace that matches the color of the spotlight mix.&quot;
&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Left: simulated spotlights. Right: the white circle shows the location of the mixed color within CIE XYZ.&lt;/p&gt;&lt;/figcaption&gt;

&lt;/figure&gt;


&lt;p&gt;
With &lt;a href=&quot;https://www.fourmilab.ch/documents/specrend/#:~:text=The%20amount%20of%20each%20primary%20to%20mix%20to%20yield%20a%20CIE%20colour%20with%20a%20given%20x%2C%20y%2C%20and%20z%20is%20the%20unknown%20J%20vector%20in%20the%20equation%3A&quot;&gt;a little matrix algebra&lt;/a&gt;, I can go the other way: CIE XYZ lets me take a color and figure out whether-and-how I can replicate it by mixing another set of colored lights. This is extremely useful! Once I know the CIE XYZ coordinates of the subpixels that make up a physical pixel on a display, I can calculate how to mix them to precisely replicate all kinds of colors. With CIE XYZ, I can dial up, say, &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: #642f99; background-color: color(xyz-d65 0.12 0.07 0.31);&quot;&gt;&lt;/span&gt;color(xyz-d65 0.12 0.07 0.31)&lt;/code&gt; on &lt;em&gt;wildly different displays&lt;/em&gt; and get &lt;em&gt;consistent results.&lt;/em&gt;

&lt;p&gt;
CIE XYZ turns color mixing problems and color matching problems into math problems. This has proven so useful that &lt;a href=&quot;https://en.wikipedia.org/wiki/ICC_profile&quot;&gt;every modern color space is defined in terms of CIE XYZ&lt;/a&gt;. When we say that a system is “color managed” what we’re saying is: it’s built on top of CIE XYZ.

&lt;p&gt;
So! CIE XYZ! It’s useful! But it’s not useful for everything.

&lt;h2 class=&quot;little-h&quot;&gt;Perceptual uniformity (is hard)&lt;/h2&gt;

&lt;p&gt;
Predicting mixes was one thing I learned in art class. Another? Creating even gradients.

&lt;figure&gt;
&lt;picture&gt;
	&lt;source
		type=&quot;image/avif&quot;
		sizes=&quot;auto, 80vw&quot;
		width=&quot;1214&quot;
		height=&quot;260&quot;
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/gradient-handdrawn-1214w.avif 1214w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-1000w.avif 1000w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-800w.avif 800w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-600w.avif 600w&quot;
	/&gt;
	&lt;source
		type=&quot;image/webp&quot;
		sizes=&quot;auto, 80vw&quot;
		width=&quot;1214&quot;
		height=&quot;260&quot;
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/gradient-handdrawn-1214w.webp 1214w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-1000w.webp 1000w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-800w.webp 800w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-600w.webp 600w&quot;
	/&gt;
	&lt;!--TODO 80vw, really? --&gt;
	&lt;img
		alt=&quot;A hand-drawn row of seven squares, each given a solid shade with cross-hatched dark blue pencil. The left-most square is pure white, the right-most square is as dark as it can get, and from left to right the in-between squares get progressively darker.&quot;
		loading=&quot;lazy&quot;
		sizes=&quot;auto, 80vw&quot;
		width=&quot;1214&quot;
		height=&quot;260&quot;
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/gradient-handdrawn-solid-1214w.jpg 1214w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-solid-1000w.jpg 1000w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-solid-800w.jpg 800w,
		        /assets/2024-02-20-okay-color-spaces/gradient-handdrawn-solid-600w.jpg 600w&quot;
		src=&quot;/assets/2024-02-20-okay-color-spaces/gradient-handdrawn-solid-600w.jpg&quot;
	/&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;Still got it&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
When you’re learning a new medium or technique – crosshatching, watercolors, whatever – this is the 101 lesson; it’s like playing scales in music. Practicing gradients gives you a facility with the tools and teaches you how to judge and create even intervals of color.

&lt;p&gt;
CIE XYZ is very bad at this. If we draw a straight line through the CIE XYZ space and mark that line at evenly-spaced intervals, sampling at each mark, we get &lt;em&gt;bad gradients.&lt;/em&gt; Like this lopsided grayscale:

&lt;!-- l: black to white --&gt;
&lt;!-- `black` -&gt; `white` --&gt;

&lt;style&gt;
.gradient {
	box-shadow:
	    0px 0.6px 0.7px hsl(0deg 0% 58% / 0.36),
    	0.1px 1.9px 2.1px -0.8px hsl(0deg 0% 58% / 0.36),
	    0.1px 4.9px 5.5px -1.7px hsl(0deg 0% 58% / 0.36),
	    0.3px 11.8px 13.3px -2.5px hsl(0deg 0% 58% / 0.36);
}
p ~ figure {
	margin-top: 1.5em;
}
&lt;/style&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven solid-colored squares in a line. They go from white on the left to black on the right, getting progressively darker as they go, but most of them are pretty light. The second-to-last looks like a middle gray, and there is a huge change between it and the last, totally-black square.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #f1f1f1; fill: color(xyz-d65 0.8327736 0.8761833 0.9542142);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #dedede; fill: color(xyz-d65 0.6948548 0.7310753 0.7961832);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #c9c9c9; fill: color(xyz-d65 0.5569361 0.5859673 0.6381522);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #b1b1b1; fill: color(xyz-d65 0.4190173 0.4408593 0.4801212);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #949494; fill: color(xyz-d65 0.2810986 0.2957513 0.3220902);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #6c6c6c; fill: color(xyz-d65 0.1431798 0.1506433 0.1640592);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #111; fill: color(xyz-d65 0.005261 0.0055353 0.0060282);&quot; /&gt;

&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;CIE XYZ gradient&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares, from white to black, just like the previous illustration, but this time the steps are a lot more even, the middle gray is in the middle, and the right half of the illustration contains some dark grays before we get to black.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #f1f1f1; fill: lab(95 0 0);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #c6c6c6; fill: lab(80 0 0);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #9e9e9e; fill: lab(65 0 0);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #777; fill: lab(50 0 0);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #525252; fill: lab(35 0 0);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #303030; fill: lab(20 0 0);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #111; fill: lab(5 0 0);&quot; /&gt;
&lt;/svg&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Something better, for comparison&lt;/p&gt;&lt;/figcaption&gt; 
&lt;/figure&gt;

&lt;p&gt;
CIE XYZ hangs out in the light-gray zone forever, and then gets very dark very fast – that last step is a doozy.

&lt;p&gt;
&lt;em&gt;All&lt;/em&gt; CIE XYZ transitions from lighter colors to darker colors suffer from this problem&lt;sup&gt;&lt;a id=&quot;fn-3-mark&quot; href=&quot;#fn-3-note&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.



&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares again, but now they go from yellow to purple. Most of them are yellow, and there is a quick drop-off to deep purple right at the end.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #ff0; fill: color(xyz-d65 0.7699751 0.9278077 0.1385256);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #eeec41; fill: color(xyz-d65 0.6616459 0.7848397 0.1671047);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #dbd65c; fill: color(xyz-d65 0.5533168 0.6418718 0.1956837);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #c6be6f; fill: color(xyz-d65 0.4449876 0.4989038 0.2242628);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #ada07f; fill: color(xyz-d65 0.3366584 0.3559359 0.2528419);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #8e798d; fill: color(xyz-d65 0.2283292 0.2129679 0.2814209);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #642f99; fill: color(xyz-d65 0.12 0.07 0.31);&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Yellow yellow yellow yellow, yellowish-gray… mauve… &lt;em&gt;purple!&lt;/em&gt; &lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Once again, seven squares from yellow to purple. This one looks a lot more even; yellows on the left, purples on the right, even steps between adjacent squares.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #ff0; fill: oklab(96.798272% -0.071369 0.1985698);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #e2de5d; fill: oklab(87.862697% -0.044469 0.1421801);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #c6bd7a; fill: oklab(78.927122% -0.017568 0.0857904);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #ab9c8a; fill: oklab(69.991547% 0.0093322 0.0294007);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #917b93; fill: oklab(61.055972% 0.0362327 -0.026989);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #795898; fill: oklab(52.120397% 0.0631331 -0.083379);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #642f99; fill: oklab(43.184822% 0.0900336 -0.139768);&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;This one has the same number of yellows and purples, and puts the middle color in the middle.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Transitions between light colors and dark colors aren’t the only problem. Gradients between different hues can look lopsided, too.&lt;sup&gt;&lt;a id=&quot;fn-4-mark&quot; href=&quot;#fn-4-note&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.



&lt;!-- h: blue to green --&gt;
&lt;!-- in theory these two colors produce significantly more lopsidedness:
oklch(66% 0.175 263)
oklch(66% 0.17258 128)
but to my eyes it&apos;s not that much more, and boy -- that&apos;s an uglier gradient 

color(xyz-d65 0.27 0.27 0.99)
= 0090FF
= 465nm

color(xyz-d65 0.12 0.27 0.02)
#00a700
~510nm


--&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares, again, now they go from a cerulean blue to green. Lots of blue and only gets green twoards the end.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #0092ff; fill: color(xyz-d65 0.27 0.27 0.99);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #0096eb; fill: color(xyz-d65 0.245 0.27 0.8283333);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #009ad4; fill: color(xyz-d65 0.22 0.27 0.6666667);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #009dba; fill: color(xyz-d65 0.195 0.27 0.505);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a199; fill: color(xyz-d65 0.17 0.27 0.3433333);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a46a; fill: color(xyz-d65 0.145 0.27 0.1816667);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a700; fill: color(xyz-d65 0.12 0.27 0.02);&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;I can barely tell the difference between the first two swatches. But things are getting green quickly, over on the right.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares from cerulean blue to green again, this time with much better balance and even steps.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #0092ff; fill: oklab(64.628218% -0.07586 -0.185598);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #009add; fill: oklab(64.249045% -0.095807 -0.129729);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #009fba; fill: oklab(63.869871% -0.115754 -0.07386);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a399; fill: oklab(63.490697% -0.135701 -0.017991);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a674; fill: oklab(63.111524% -0.155648 0.037878);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a746; fill: oklab(62.73235% -0.175596 0.0937469);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00a700; fill: oklab(62.353176% -0.195543 0.1496158);&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;Better.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My favorite perceptual problem  with CIE XYZ is that transitions between blue and gray get weirdly purple in the middle:

&lt;!-- c: blue to gray --&gt;
&lt;!-- `blue` -&gt; lab() lightness of `blue` with zero chroma and hue... oklab(45.201372% 0 0) --&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares again. This time they go from blue to gray. In the middle, the squares look purplish.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00f; fill: color(xyz-d65 0.1804808 0.0721923 0.9505322);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #2121ed; fill: color(xyz-d65 0.1650304 0.0755526 0.8088732);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #3131d9; fill: color(xyz-d65 0.1495799 0.0789128 0.6672143);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #3d3dc3; fill: color(xyz-d65 0.1341295 0.0822731 0.5255554);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #4646a9; fill: color(xyz-d65 0.1186791 0.0856333 0.3838965);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #4e4e87; fill: color(xyz-d65 0.1032287 0.0889936 0.2422376);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #565656; fill: color(xyz-d65 0.0877782 0.0923538 0.1005787);&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Abney_effect&quot;&gt;Blue lights actually do this in real life!&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares from blue to gray again. This time they do not get purplish in the middle, they just look grayish-bluish.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00f; fill: oklab(45.201372% -0.032457 -0.311528);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #0133e3; fill: oklab(45.201372% -0.027047 -0.259607);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #1444c7; fill: oklab(45.201372% -0.021638 -0.207685);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #274eab; fill: oklab(45.201372% -0.016228 -0.155764);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #385390; fill: oklab(45.201372% -0.010819 -0.103843);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #475673; fill: oklab(45.201372% -0.005409 -0.051921);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #565656; fill: oklab(45.201372% 0 0);&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;
&lt;p&gt;True blue, all the way through.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Color nerds call color spaces that are &lt;em&gt;good&lt;/em&gt; at creating even gradients “perceptually uniform.” Meaning, the &lt;em&gt;distance&lt;/em&gt; between any two colors in the space corresponds to “how different” they look, to people. There are all sorts of reasons folks might want a color space like this.

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cynthia_Brewer#Brewer_palettes&quot;&gt;Maybe&lt;/a&gt; they’re making a data visualization and want to use color differences to communicate value differences.
&lt;li&gt;&lt;a href=&quot;https://helpx.adobe.com/photoshop-elements/using/adjusting-color-saturation-hue-vibrance.html&quot;&gt;Maybe&lt;/a&gt; they’re making image-editing software, and want modifications to feel consistent and intuitive.&lt;sup&gt;&lt;a id=&quot;fn-5-mark&quot; href=&quot;#fn-5-note&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/LMS_color_space#Image_processing&quot;&gt;Maybe&lt;/a&gt; they’re storing colors digitally, and want to store as many perceptually-different colors in as few bits as possible.
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html&quot;&gt;Maybe&lt;/a&gt; they’re trying to measure color contrast in order to ensure accessibility.
&lt;li&gt;&lt;a href=&quot;https://gradient.style/&quot;&gt;Maybe&lt;/a&gt; they’re trying to render a nice, even-looking gradient as a design element.
&lt;/ul&gt;

&lt;p&gt;
For these tasks (and &lt;a href=&quot;https://en.wikipedia.org/wiki/Beer_measurement#Colour&quot;&gt;more&lt;/a&gt;!), a perceptually-uniform color space is the right tool for the job.

&lt;p&gt;
As discussed, CIE XYZ ain’t it.

&lt;p&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/SRGB&quot;&gt;sRGB&lt;/a&gt;, the web’s dominant, default color space, was constructed in order to model a typical 1990s cathode-ray tube display; sRGB also ain’t it.&lt;sup&gt;&lt;a id=&quot;fn-6-mark&quot; href=&quot;#fn-6-note&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;)

&lt;p&gt;
The first attempt at “it” (a perceptually-uniform color space) was made by &lt;a href=&quot;https://en.wikipedia.org/wiki/Albert_Henry_Munsell&quot;&gt;Albert Munsell&lt;/a&gt;, who &lt;a href=&quot;https://www.gutenberg.org/files/26054/26054-h/26054-h.htm#tag5&quot;&gt;described his space in 1905&lt;/a&gt; (charmingly, as a &lt;span class=&quot;small-caps&quot;&gt;“COLOR TREE”&lt;/span&gt;) and &lt;a href=&quot;https://library.si.edu/digital-library/book/atlasmunsellcol00muns&quot;&gt;published an “Atlas” to it in 1913&lt;/a&gt;. 

&lt;p&gt;
The central “trunk” of Munsell’s &lt;span class=&quot;small-caps&quot;&gt;TREE&lt;/span&gt; goes from black at the bottom, through grey in the middle, to white at the top. The middle expands outwards into a rainbow of “branches”, with each angle around the trunk representing a particular hue. Each branch starts off desaturated near the middle, and gets more and more saturated the further out it goes.

&lt;figure class=&quot;wide&quot;&gt;
&lt;div class=&quot;surfaceAndCrossectionContainer&quot; style=&quot;align-items: center;&quot;&gt;
&lt;div&gt;&lt;picture&gt;
	&lt;source
		type=&quot;image/avif&quot;
		sizes=&quot;auto, (min-width: 400px) 50vw, 100vw&quot;
		width=&quot;1446&quot;
		height=&quot;1504&quot;		
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/color-tree-transparent-1446w.avif 1446w,
				/assets/2024-02-20-okay-color-spaces/color-tree-transparent-1200w.avif 1200w,
				/assets/2024-02-20-okay-color-spaces/color-tree-transparent-1000w.avif 1000w,
				/assets/2024-02-20-okay-color-spaces/color-tree-transparent-800w.avif  800w,
				/assets/2024-02-20-okay-color-spaces/color-tree-transparent-600w.avif  600w&quot;
	/&gt;
	&lt;img
		loading=&quot;lazy&quot;
		sizes=&quot;auto, (min-width: 400px) 50vw, 100vw&quot;
		width=&quot;1446&quot;
		height=&quot;1504&quot;
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/color-tree-solid-1446w.jpg 1446w,
				/assets/2024-02-20-okay-color-spaces/color-tree-solid-1200w.jpg 1200w,
				/assets/2024-02-20-okay-color-spaces/color-tree-solid-1000w.jpg 1000w,
				/assets/2024-02-20-okay-color-spaces/color-tree-solid-800w.jpg  800w,
				/assets/2024-02-20-okay-color-spaces/color-tree-solid-600w.jpg  600w&quot;
		src=&quot;/assets/2024-02-20-okay-color-spaces/color-tree-solid-600w.jpg&quot;
		alt=&quot;An old-timey-looking black-and-white line engraving of a tree, with the trunk divided into nine numbered segments (1-9). Branches extend outwards. Each branch has a sign hanging off of it describing its hue (“Yellow”, “Red”, “Yellow-Red”, “Green-Yellow”, “Blue-Green”, “Purple”, “Purple-Blue”, “Red-Purple”, “Blue”, and “Green”). Each branch has numbered leaves - the numbers go higher as they get further from the trunk. Branches have different numbers of leaves; for instance, the reds go to 10, but the purples only go to 6. There are many delightful illustrated details: a sun with a face on it shining down from the top of the image, an artist with a trusty dog painting a canvas in the lower right, a squirrel burrowing under the base of the tree, billowing clouds, rolling hills.&quot;
	/&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;div&gt;&lt;picture&gt;
	&lt;source
		type=&quot;image/avif&quot;
		sizes=&quot;auto, (min-width: 400px) 50vw, 100vw&quot;
		width=&quot;1280&quot;
		height=&quot;960&quot;
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/Munsell_1943-1280w.avif 1280w,
		        /assets/2024-02-20-okay-color-spaces/Munsell_1943-800w.avif  800w,
		        /assets/2024-02-20-okay-color-spaces/Munsell_1943-600w.avif  600w&quot;
	/&gt;
	&lt;source
		type=&quot;image/webp&quot;
		sizes=&quot;auto, (min-width: 400px) 50vw, 100vw&quot;
		width=&quot;1280&quot;
		height=&quot;960&quot;
		srcset=&quot;/assets/2024-02-20-okay-color-spaces/Munsell_1943-1280w.webp 1280w,
		        /assets/2024-02-20-okay-color-spaces/Munsell_1943-800w.webp  800w,
		        /assets/2024-02-20-okay-color-spaces/Munsell_1943-600w.webp  600w&quot;
	/&gt;
	&lt;img
		loading=&quot;lazy&quot;
		sizes=&quot;auto, (min-width: 400px) 50vw, 100vw&quot;
		width=&quot;1280&quot;
		height=&quot;960&quot;
		src=&quot;/assets/2024-02-20-okay-color-spaces/Munsell_1943-600w.png&quot;
		alt=&quot;A colorful, modern, and clean 3D visualization. It consists of a bunch of stacked rings made of little cubic wedges, each with a solid color. It almost looks like voxel art. All together they make an rough, irregular, lopsided shape that starts at a single small black cylinder at the bottom, grows out to a huge colorful pinwheel of colors, and shrinks back to a single small white cylinder white at the top. A quarter of the solid has been cleanly cut away to the core, letting us see two planes of squares - all with constant hue but varying lightness and chroma. The core is achromatic and goes from black through gray up to white. Each step away from it gets more and more chromatic. The furthest tips on each cutaway plane are the most chromatic. The left plane of the cutaway has a magenta hue and the right, blue. The rest of the stacked rings are colored like the rainbow: orange, yellow, green, and cyan. Presumably missing, in the cutaway section, are red and purple.&quot;
	/&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;
&lt;p&gt;
Left: A wood engraving illustrating Munsell’s &lt;span class=&quot;small-caps&quot;&gt;COLOR TREE&lt;/span&gt; from &lt;a href=&quot;https://archive.org/details/gri_c00033125006531145/page/n21/mode/2up&quot;&gt;&lt;cite&gt;A Grammar of Color&lt;/cite&gt;&lt;/a&gt; by T. M. Cleland, 1921. Right: An excellent cutaway visualization of the &lt;a href=&quot;https://www.rit.edu/science/munsell-color-science-lab-educational-resources#munsell-renotation-data&quot;&gt;Munsell Renotation Data&lt;/a&gt; by the stellar Wikipedian, &lt;a href=&quot;https://commons.wikimedia.org/wiki/User:Datumizer&quot;&gt;Mike Horvath, &lt;abbr class=&quot;small-caps&quot;&gt;aka&lt;/abbr&gt; Datumizer&lt;/a&gt;
&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Whereas every color theorist before Albert Munsell (and many, after him) worked from the “frame in”, trying to cram all visible colors into a regular shape like a wheel or a sphere or whatever, Munsell instead worked from the “content out”, trying to create &lt;em&gt;even intervals&lt;/em&gt; between adjacent colors and letting each “branch” extend as far as it could before he reached some limit of saturation. The resulting solid resembles a lumpy, lopsided spinning top.


&lt;p&gt;
Albert’s &lt;span class=&quot;small-caps&quot;&gt;COLOR TREE&lt;/span&gt; was immediately recognized as useful, and is in fact &lt;a href=&quot;https://en.wikipedia.org/wiki/Munsell_color_system#:~:text=the%20Munsell%20system%20is%20still%20widely%20used&quot;&gt;still used&lt;/a&gt;. But as time passed, problems accumulated:

&lt;ul&gt;
&lt;li&gt;It was based on his own personal judgements rather than scientific experiments, &lt;a href=&quot;https://www.rit.edu/science/munsell-color-science-lab-educational-resources#munsell-renotation-data&quot;&gt;&lt;em&gt;and it showed.&lt;/em&gt;&lt;/a&gt;
&lt;li&gt;(Just like our toy color space!) the &lt;span class=&quot;small-caps&quot;&gt;COLOR TREE&lt;/span&gt; is discrete, and only specifies the positions of a few hundred individual colors.
&lt;li&gt;Those colors didn’t cover anything close to the range of colors that people can actually see.
&lt;/ul&gt;

&lt;p&gt;
In the decades that followed, people tried to solve all of these problems, both iterating on Munsell’s tree and creating wholly new continuous color spaces that attempted to be perceptually uniform. Arguably, the most significant of those spaces was constructed by the CIE, in 1976: &lt;a href=&quot;https://en.wikipedia.org/wiki/CIELAB_color_space&quot;&gt;CIELAB&lt;/a&gt;. (Ever heard of “Lab color”? This is that.)

&lt;p&gt;
CIELAB is a relatively simple mathematical transform of CIE XYZ, making it easy to implement in “color managed” digital contexts. But – tragically! – &lt;a href=&quot;https://www.w3.org/Graphics/Color/Workshop/slides/talk/lilley#limit&quot;&gt;CIELAB isn’t exactly perceptually uniform&lt;/a&gt;. Worse, the more experiments people did, the clearer it became that &lt;em&gt;no&lt;/em&gt; three-dimensional space &lt;em&gt;could ever&lt;/em&gt; be perceptually uniform; three dimensions just cannot capture all of the weird and wonderful ways that our eyes and brains process color comparisons. As anyone who has &lt;a href=&quot;https://massmoca.org/event/james-turrell/&quot;&gt;entered a Turrell&lt;/a&gt; or debated &lt;a href=&quot;https://en.wikipedia.org/wiki/The_dress&quot;&gt;The Dress&lt;/a&gt; can tell you, color perception is &lt;em&gt;wild.&lt;/em&gt; When trying to predict how people are going to perceive the difference between two colors, we need to account for &lt;em&gt;way&lt;/em&gt; more than three variables. For instance:

&lt;ul&gt;
&lt;li&gt;How large are the color samples? Where are they in the subject’s field of vision?
&lt;li&gt;How long have they been there? What other colors were there recently?
&lt;li&gt;Crucially, what other colors surround the samples?
&lt;li&gt;What’s the ambient background lighting like?
&lt;/ul&gt;

&lt;p&gt;
(I’m not even going to mention the ways in which we “read” scenes within &lt;a href=&quot;https://lsm.media.mit.edu/papers/kubat_4_29.pdf&quot;&gt;semantic&lt;/a&gt; and &lt;a href=&quot;https://radiolab.org/podcast/211213-sky-isnt-blue&quot;&gt;cultural&lt;/a&gt; contexts, which also matter.)
&lt;/p&gt;

&lt;p&gt;
So, after 1976, people started developing more-and-more complicated models that attempted to account for more-and-more of this complexity. &lt;a href=&quot;https://en.wikipedia.org/wiki/Color_appearance_model#Color_appearance_models&quot;&gt;We’ve spent fifty years coming up with these things&lt;/a&gt;. Many of the resulting color models are considered too complex for most practical applications, and yet none of them is considered perfect.

&lt;p&gt;
I am fascinated that the CIE knocked &lt;em&gt;predicting color mixes and matches&lt;/em&gt; out of the park in 1931, and yet here we are, almost a hundred years later, still trying to solve the problem of &lt;em&gt;predicting color differences.&lt;/em&gt; Our mastery of mixing and matching makes us very good at capturing and replicating colors. But, because we can’t predict differences well, we’re still bad at automating all sorts of other color tasks, which remain as much art as science.

&lt;p&gt;
One thing that’s absolutely clear is that the problem of perceptual uniformity is &lt;em&gt;never&lt;/em&gt; going to be solved with a plain-Jane three-dimensional color space. But! The entire universe of digital imaging is rooted in such spaces, because it’s all built on top of CIE XYZ.

&lt;p&gt;
So – what is someone who wants a perceptually-uniform space in a digital context supposed to do these days?

&lt;h2 class=&quot;little-h&quot;&gt;Oklab: it’s okay!&lt;/h2&gt;

&lt;p&gt;
Until a few years ago, the best tool for these sorts of jobs was still CIELAB. So I was excited when &lt;a href=&quot;https://www.w3.org/TR/2016/WD-css-color-4-20160705/#lab-colors&quot;&gt;CIELAB-for-the-web was first proposed in 2016&lt;/a&gt;. And then I waited for five years, &lt;a href=&quot;https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes#CSS&quot;&gt;until it finally shipped&lt;/a&gt;.

&lt;p&gt;
While I was waiting, in December of 2020, a guy named &lt;a href=&quot;https://bottosson.github.io/posts/oklab/&quot;&gt;Björn Ottosson wrote a blog post&lt;/a&gt;. In it, he introduced a brand-new three-dimensional color space that he’d been working on: Oklab.

&lt;p&gt;
In an over-simplified nutshell, here’s how Björn came up with Oklab:

&lt;ol&gt;
&lt;li&gt;He picked a couple of best-in-class color models&lt;a id=&quot;fn-7-mark&quot; href=&quot;#fn-7-note&quot;&gt;&lt;sup&gt;7&lt;/sup&gt;&lt;/a&gt;.
&lt;li&gt;After choosing a set of values representing “normal” viewing conditions, he used those models to generate a set of color comparison data.
&lt;li&gt;He transformed CIE XYZ in a mathematically clean/easy way to &lt;em&gt;approximately&lt;/em&gt; fit all of that color comparison data &lt;em&gt;pretty well.&lt;/em&gt;
&lt;/ol&gt;

&lt;p&gt;
&lt;i lang=&apos;fr&apos;&gt;Et voila:&lt;/i&gt; Oklab. Here’s what it looks like:

&lt;figure class=wide&gt;
&lt;div class=&quot;inputContainer&quot;&gt;
	&lt;label class=&quot;info-line&quot; for=l&gt;Cross section &lt;math&gt;&lt;mi&gt;L&lt;/mi&gt;&lt;/math&gt;&lt;/label&gt;
	&lt;input id=l type=&quot;range&quot; min=0 max=1 step=0.01 value=0.5&gt;
	&lt;output for=l&gt;&lt;/output&gt;
&lt;/div&gt;

&lt;div class=&quot;surfaceAndCrossectionContainer&quot;&gt;
	&lt;div
		class=&quot;oklabContainer&quot;
		id=&quot;oklab-solid&quot;
		aria-role=&quot;img&quot;
		arial-label=&quot;Another 3D graph of a color solid. Unlike the XYZ solid, this one is not cubic at all, and has curved sides and lines. It’s centered around the (vertical) L axis, and almost looks like an ice cream cone. At the bottom is black; the shape swells out into a cone/ring of colors, and then arches/points up to white at the top. The points that are the furtest away from the L axis are the most colorful/pure. There are cross axes, along the “floor,” labelled “a” and “b”, that each go from -0.4 to +0.4. L goes from 0 to 1.&quot;
	&gt;
	&lt;/div&gt;
	&lt;div
		class=crosssectionContainer
		aria-role=&quot;img&quot;
		arial-label=&quot;A 2D cross-section of the Oklab solid at a given value of L, showing a Cartesian plane with “a” and “b” axes going from -0.4 to +0.4. When L is small, the cross-section is small and dark. It grows into a kind of billowing triangle sail shape with curved sides, untl about L=0.5, when it starts morphing into a rainbow parallelogram. At L values above 0.8 it shrinks quickly into a light, very skinny/tall sail of a shape, and finally shrinks to a single white point. At every L value, the value at a=b=0 is achromatic - a gray of varying lightness, and the further away from it we get, the more colorful the pixels. All of the hues are arrayed around the origin, so that if you were to travel the outside perimeter of the shape at any L level, you would see a rainbow gradient: moving clockwise from 12 o’clock we start at yellow, with orange at one o’clock and red at two, magenta at three, purple from four to five, indigo at six, cerulean blue at seven, teal from eight to nine, greenest green at 10:30, and back to yellow at noon.&quot;
	&gt;
		&lt;canvas id=&quot;oklab-crosssection&quot;&gt;&lt;/canvas&gt;
		&lt;img src=&quot;assets/crosssection-background-oklab.svg&quot; style=&quot;width: 100%;&quot;&gt;
	&lt;/div&gt;
&lt;/div&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;The P3 gamut, plotted in the Oklab color space.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;
Is it perfect? &lt;a href=&quot;https://front-end.social/@eeeps/111443549539978889&quot;&gt;No&lt;/a&gt;, it is &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/9449#issuecomment-1754094385&quot;&gt;not&lt;/a&gt;. But is it mathematically and computationally simple? Yes! And does it perform better at most tasks requiring perceptual uniformity than all of the other simple, three-dimensional color spaces? Sure seems like it!

&lt;p&gt;
What happened next was astonishing: &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6642&quot;&gt;the web platform rapidly adopted Oklab&lt;/a&gt;. Oklab went from a blog post to shipping in Safari in &lt;em&gt;fifteen months!&lt;/em&gt; Wild!

&lt;p&gt;
&lt;a href=&quot;https://helpx.adobe.com/photoshop/using/gradient-interpolation.html&quot;&gt;Photoshop soon followed suit.&lt;/a&gt;

&lt;p&gt;
So, Oklab: if you want perceptual ~uniformity, it’ll do.

&lt;h2 class=&quot;little-h&quot;&gt;OKLCH, because Munsell was a good API designer&lt;/h2&gt;

&lt;p&gt;
What do Oklab’s &lt;math&gt;&lt;mi&gt;L&lt;/mi&gt;&lt;/math&gt;, &lt;math&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/math&gt;, and &lt;math&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/math&gt; parameters &lt;em&gt;actually mean?&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;&lt;math&gt;&lt;mi&gt;L&lt;/mi&gt;&lt;/math&gt; is lightness. This represents how light or dark a color looks. &lt;math&gt;&lt;mi&gt;L&lt;/mi&gt;&lt;/math&gt; goes from zero to one.
&lt;li&gt;&lt;math&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/math&gt; is “greeness-to-redness” with gray at zero. It’s theoretically unbounded but in practice it goes from around -0.4 to +0.4.
&lt;li&gt;&lt;math&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/math&gt; is “blueness-to-yellowness” with gray at zero. Same-same with the practical approximate -0.4 to +0.4 range.
&lt;/ul&gt;

&lt;p&gt;
So, in Oklab, &lt;a href=&quot;https://www.w3.org/TR/css-color-4/#valdef-color-blueviolet&quot;&gt;&lt;code&gt;blueviolet&lt;/code&gt;&lt;/a&gt; is &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: blueviolet; background-color: oklab(53.38% 0.1303 -0.2137);&quot;&gt;&lt;/span&gt;oklab(53.38% 0.1303 -0.2137)&lt;/code&gt;: medium lightness, somewhat red, and rather blue.

&lt;p&gt;
Are you thinking what I&apos;m thinking? &lt;math&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/math&gt; and &lt;math&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/math&gt; are &lt;em&gt;weird.&lt;/em&gt;

&lt;p&gt;
Munsell’s &lt;span class=&quot;small-caps&quot;&gt;COLOR TREE&lt;/span&gt; has much nicer dimensions:

&lt;ul&gt;
&lt;li&gt;Lightness: height up the tree; black to white.
&lt;li&gt;Hue: angle around the trunk; just like the color wheels of my youth.
&lt;li&gt;Chroma: how far away from the trunk are we? Gray in the middle and more saturated the further away we get.
&lt;/ul&gt;

&lt;p&gt;
These three dimensions seem to express ways that our brains actually process color. When comparing one color to another on the same branch of Munsell’s &lt;span class=&quot;small-caps&quot;&gt;COLOR TREE&lt;/span&gt;, it feels like only one thing has changed: chroma. Likewise when we go up and down the tree, or around it – both lightness and hue &lt;em&gt;feel&lt;/em&gt; like independent variables. Whereas when we change, say, the &lt;math&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/math&gt; of an Oklab color, it feels like we’re changing two things – both the hue and the chroma – simultaneously. It’s not easy to predict how changing &lt;math&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/math&gt; or &lt;math&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/math&gt; is going to look.

&lt;p&gt;
Thankfully – turns out! – Oklab was explicitly designed so that movement up/down, in/out, and around the &lt;math&gt;&lt;mi&gt;L&lt;/mi&gt;&lt;/math&gt; axis works exactly like navigating Munsell’s &lt;span class=&quot;small-caps&quot;&gt;COLOR TREE&lt;/span&gt;. Each type of movement changes &lt;em&gt;just one, psychologically-independent thing&lt;/em&gt;: the lightness, chroma, or hue of the color. In order to navigate Oklab like this, we need to use a polar coordinate system, instead of a rectangular one. When we do, we refer to the space by another name: OKLCH&lt;a id=&quot;fn-8-mark&quot; href=&quot;#fn-8-note&quot;&gt;&lt;sup&gt;8&lt;/sup&gt;&lt;/a&gt;.



&lt;figure class=&quot;wide&quot;&gt;

&lt;div class=&quot;surfaceAndCrossectionContainer&quot;&gt;
&lt;div&gt;&lt;picture&gt;
	&lt;source type=&quot;image/avif&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/crosssection-oklab-blueviolet-avif.svg&quot; /&gt;
	&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/crosssection-oklab-blueviolet-webp.svg&quot; /&gt;
	&lt;img
		src=&quot;/assets/2024-02-20-okay-color-spaces/crosssection-oklab-blueviolet-png.svg&quot;
		alt=&quot;A crossection of the Oklab space, placed on a rectilinear grid, with a horizontal “a” axis and vertical “b” axis. An irregular shape extends from about -0.2 to +0.25 “a”, and -0.3 to +0.15 “b”. It looks kind of like a rounded coffee cup, with a flatish top and bottom, and the right side convex bulging out, but the left side is concave, bending in towards the origin. The origin is a medium-dark gray; as we move away from it the colors get more and more colorful; as we rotate around it  with the same progression of colors as we move around the clock described previously. A small, heavy white circle is in the lower right-hand quadrant, circling a purple color.&quot;
	&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;div&gt;&lt;picture&gt;
	&lt;source type=&quot;image/avif&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/crosssection-oklch-blueviolet-avif.svg&quot; /&gt;
	&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/crosssection-oklch-blueviolet-webp.svg&quot; /&gt;
	&lt;img
		src=&quot;/assets/2024-02-20-okay-color-spaces/crosssection-oklch-blueviolet-png.svg&quot;
		alt=&quot;A crossection of the OKLCH space. We see the exact same rainbow-coffe-cup, at the exact same scale and position, as the Oklab crossection we just examined. However, instead of a rectilinear grid, it is now placed within polar coordinates - it looks like a dart board, with concentric circles and spokes of a wheel. Movement around the wheel is labeled “H”, and movement in/out along any spoke is labelled “C”. The small heavy white circle around a particular purple is in the same place as before, too.&quot;
	&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;figcaption class=&quot;info-line center&quot;&gt;&lt;p&gt;
Same same; &lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: #8a2be2; background-color: oklab(53.38% 0.1303 -0.2137);&quot;&gt;&lt;/span&gt;oklab(53.38% 0.1303 -0.2137)&lt;/code&gt; =
&lt;code&gt;&lt;span class=&quot;swatch&quot; style=&quot;background-color: #8a2be2; background-color: oklch(53.38% 0.2503 301.4deg);&quot;&gt;&lt;/span&gt;oklch(53.38% 0.2503 301.4deg)&lt;/code&gt;.
&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I should note that OKLCH was &lt;a href=&quot;https://en.wikipedia.org/wiki/HCL_color_space&quot;&gt;not the first color space to adopt Munsell’s lightness-chroma-hue “API”&lt;/a&gt;; even CIELAB had a polar version that worked like this, called LCH. But &lt;a href=&quot;https://raphlinus.github.io/color/2021/01/18/oklab-critique.html#hue-linearity&quot;&gt;OKLCH does appear to be one of the best&lt;/a&gt;.

&lt;p&gt;
Both OKLCH and Oklab have their uses. Gradients in polar spaces work differently than gradients in rectangular spaces. They’re not better or worse, mind you – just different. 

&lt;p&gt;
In a rectangular space, a gradient between two colors that lie on opposite sides of the space gets gray in the middle:

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Again with the seven squares. This time we&apos;re going from orange to blue. The orange gets progressively desaturated til we hit the middle square, which is perfectly gray. Then the gray gets bluer until we hit the last blue square.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #e58042; fill: oklab(70% 0.093847 0.1118425);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #cf8c66; fill: oklab(70% 0.0625647 0.0745617);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #b89684; fill: oklab(70% 0.0312823 0.0372808);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #9e9e9e; fill: oklab(70% 0 0);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #80a5b7; fill: oklab(70% -0.031282 -0.037281);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #57aacf; fill: oklab(70% -0.062565 -0.074562);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00ade7; fill: oklab(70% -0.093847 -0.111842);&quot; /&gt;
&lt;/svg&gt;
&lt;picture&gt;
	&lt;source type=&quot;image/avif&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/oklab-interpolation-avif.svg&quot; /&gt;
	&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/oklab-interpolation-webp.svg&quot; /&gt;
	&lt;img
		style=&quot;width: 80%; margin-bottom: -6%; margin-top: -4%;&quot;
		src=&quot;/assets/2024-02-20-okay-color-spaces/oklab-interpolation-png.svg&quot;
		alt=&quot;A crossection of the Oklab solid, with the rectilinear grid and “a” and “b” axes, as previously described. This particular crossection is like a parallelogram with four corners - green in the upper left, orange in the upper right, magenta in the lower right, and blue in the lower left. But the magenta edge has been, like, clipped, as if someone chopped off the point of it. Anyways there is a series of seven white circles, each circling one of the colors in the above seven-step-gradient. They are perfectly evenly spaced and on a perfect straight line from the bottom left to top right. The fourth circle is right at the origin, and circles a pure gray.&quot;
	&gt;
&lt;/picture&gt;
&lt;/figure&gt;

&lt;p&gt;
In a polar space, instead of drawing a straight line through the space to get from one color to the other, we orbit the origin, creating a half-rainbow of evenly-separated hues.
&lt;/p&gt;

&lt;figure&gt;
&lt;svg
	class=&quot;gradient&quot;
	viewbox=&quot;0 0 700 100&quot;
	aria-role=&quot;img&quot;
	aria-label=&quot;Seven squares, from the same orange to the same blue. But now instead of getting gray in the middle, they get yellow – green in the middle – teal – then blue.&quot;
&gt;
&lt;rect x=&quot;0&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #e58042; fill: oklch(0.7 0.146 50);&quot; /&gt;
&lt;rect x=&quot;100&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #cd9200; fill: oklch(0.7 0.146 80);&quot; /&gt;
&lt;rect x=&quot;200&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #a5a51b; fill: oklch(0.7 0.146 110);&quot; /&gt;
&lt;rect x=&quot;300&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #6ab459; fill: oklch(0.7 0.146 140);&quot; /&gt;
&lt;rect x=&quot;400&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00bb90; fill: oklch(0.7 0.146 170);&quot; /&gt;
&lt;rect x=&quot;500&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00b7c0; fill: oklch(0.7 0.146 200);&quot; /&gt;
&lt;rect x=&quot;600&quot; width=&quot;100&quot; height=&quot;100&quot; style=&quot;fill: #00ade7; fill: oklch(0.7 0.146 230);&quot; /&gt;
&lt;/svg&gt;
&lt;picture&gt;
	&lt;source type=&quot;image/avif&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/oklch-interpolation-avif.svg&quot; /&gt;
	&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/2024-02-20-okay-color-spaces/oklch-interpolation-webp.svg&quot; /&gt;
	&lt;img
		style=&quot;width: 80%; margin-bottom: -6%; margin-top: -6%;&quot;
		src=&quot;/assets/2024-02-20-okay-color-spaces/oklch-interpolation.svg&quot;
		alt=&quot;A crossection of the OKLCH space. Same clipped-corner parallelogram as before, but now it’s on the dartboard/wheel/polar grid. Again there are seven circles, but this time, instead of being aligned in a straight line right through the origin, from the blue to the orange, we have a perfect half-circle around the origin. Interestingly, the circles around the teal and and yellow colors are getting kind of close to leaving the parallelogram – being outside the P3 gamut – but they stay in bounds.&quot;
	&gt;
&lt;/picture&gt;
&lt;/figure&gt;


&lt;p&gt;
Some tasks (like measuring color difference) are simpler in rectangular spaces. But whenever we want to change the hue or chroma of a color independently – say, when we want to turn a color’s saturation up a notch, or &lt;a href=&quot;https://web.dev/articles/building/a-color-scheme&quot;&gt;theme and scheme with code&lt;/a&gt; – only a perceptually ~uniform, &lt;em&gt;polar&lt;/em&gt; space will do. Right now, on the web: that means OKLCH.

&lt;h2 class=&quot;little-h&quot;&gt;So!&lt;/h2&gt;

&lt;p&gt;What have we learned?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Color spaces are constructs, which arrange colors into some coordinate-based space.
&lt;li&gt;Colors don’t have any kind of innate spatial relationship to each other, but arranging them in various ways can be useful.
&lt;li&gt;CIE XYZ is an extremely useful color space, because it turns color mixing problems and color matching problems into math problems.
&lt;li&gt;All “color managed” digital imaging is based on CIE XYZ.
&lt;li&gt;Measuring how we’ll perceive the &lt;em&gt;difference&lt;/em&gt; between two colors is a whole other kettle of fish; predicting differences remains an unsolved problem.
&lt;li&gt;Solving that problem – by constructing a model with perfect “perceptual uniformity” – is going to require more than three variables, but oops! We built all digital imaging on top of CIE XYZ, a three-dimensional space.
&lt;li&gt;Oklab tries to resolve this by making many assumptions and simplifications, in order to model everything we’ve learned about perceptual uniformity in a simple three-dimensional space.
&lt;li&gt;Oklab is pretty good!
&lt;li&gt;People tend to think about color in terms of three variables: lightness, chroma, and hue.
&lt;li&gt;Oklab does a good job of isolating these variables, but in order to use them, we have to navigate it using polar coordinates instead of rectangular ones. When we navigate Oklab this way, we call it OKLCH.
&lt;/ul&gt;

&lt;h2 class=&quot;little-h&quot;&gt;Time to play&lt;/h2&gt;

&lt;p&gt;Enough reading! Go! Explore!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://oklch.com/&quot;&gt;Evil Martians’ OKLCH color picker&lt;/a&gt; is fantastic.&lt;/li&gt;
&lt;li&gt;I couldn’t have written this post without &lt;a href=&quot;https://colorjs.io&quot;&gt;Color.js&lt;/a&gt;. It’s doing all of the color math in the interactive visualizations, and I had multiple &lt;a href=&quot;https://colorjs.io/notebook/&quot;&gt;color notebooks&lt;/a&gt; open at all times, while writing.
&lt;li&gt;Speaking of the interactive illustrations, if you want to visualize &lt;em&gt;other&lt;/em&gt; gamuts in &lt;em&gt;other&lt;/em&gt; color spaces, &lt;a href=&quot;https://facelessuser.github.io/coloraide/demos/3d_models.html&quot;&gt;the ColorAide &lt;cite&gt;Interactive 3D Color Space Models&lt;/cite&gt; demo by Isaac Muse&lt;/a&gt; and &lt;a href=&quot;https://hueplot.ardov.me&quot;&gt;&lt;cite&gt;Hueplot&lt;/cite&gt; by Alexey Ardov&lt;/a&gt; have you covered.&lt;/li&gt;
&lt;li&gt;For a different entry point into understanding color spaces in general and CIE XYZ in particular, check out &lt;a href=&quot;https://ciechanow.ski/color-spaces/&quot;&gt;Bartosz Ciechanowski’s &lt;cite&gt;Color Spaces&lt;/cite&gt;&lt;/a&gt;. It’s chock full of interactives.
&lt;li&gt;I know I linked to it earlier, but &lt;a href=&quot;https://bottosson.github.io/posts/oklab/&quot;&gt;Björn Ottosson’s original Oklab blog post&lt;/a&gt; is very good.
&lt;/ul&gt;

&lt;p&gt;Okay, bye! 🌈&lt;/p&gt;


&lt;aside class=&apos;footnotes&apos;&gt;
&lt;ol start=1&gt;
&lt;li id=&quot;fn-1-note&quot;&gt;…for colored &lt;em&gt;light,&lt;/em&gt; not colored pigments. &lt;a href=&quot;https://larswander.com/writing/spectral-paint-curves/&quot;&gt;Predicting how pigments will mix is hard&lt;/a&gt;. &lt;a href=&quot;#fn-1-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2-note&quot;&gt;
In addition to containing all of the &lt;em&gt;visible colors,&lt;/em&gt; CIE XYZ contains a bunch of &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Impossible_color#Imaginary_colors&quot;&gt;imaginary colors&lt;/a&gt;,&lt;/em&gt; which are, uh, outside of the scope of this article.
&lt;a href=&quot;#fn-2-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-3-note&quot;&gt;…because CIE XYZ represents intensity linearly, but &lt;a href=&quot;https://en.wikipedia.org/wiki/Weber–Fechner_law#Vision&quot;&gt;like all of our senses, human vision responds ~logarithmically to input&lt;/a&gt;. &lt;a href=&quot;#fn-3-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-4-note&quot;&gt;…because &lt;a href=&quot;https://en.wikipedia.org/wiki/Luminous_efficiency_function&quot;&gt;we’re  more sensitive to some hues than others&lt;/a&gt;. An implication of this is that there are many times more photons shooting out of the blue side of that gradient, than there are from the green side, even though every swatch on the gradient has the same apparent lightness. Weird! &lt;a href=&quot;#fn-4-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-5-note&quot;&gt;Many image editing operations (for instance: compositing and downscaling) require a color space like CIE XYZ that models &lt;em&gt;how light works&lt;/em&gt;. But many others (for instance: changing hue, saturation, and/or lightness) benefit from being done in a perceptually uniform space that models &lt;em&gt;how our eyes and brains process light.&lt;/em&gt;&lt;a href=&quot;#fn-5-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-6-note&quot;&gt;Although, interestingly, &lt;a href=&quot;https://en.wikipedia.org/wiki/Cathode-ray_tube#Gamma&quot;&gt;cathode-ray tubes respond to input non-linearly, kind-of-sort-of like people do&lt;/a&gt;. &lt;a href=&quot;#fn-6-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-7-note&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Color_appearance_model#CAM16&quot;&gt;CAM16-UCS&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Color_appearance_model#IPT&quot;&gt;IPT&lt;/a&gt;. &lt;a href=&quot;#fn-7-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-8-note&quot;&gt;&lt;a href=&quot;https://front-end.social/@eeeps/109955851076325361&quot;&gt;Pronounced “Oklachroma”&lt;/a&gt;. &lt;a href=&quot;#fn-8-mark&quot;&gt;↩︎&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/aside&gt;


&lt;script type=&quot;module&quot; crossorigin src=&quot;./assets/index-mXX5eTHP.js&quot;&gt;&lt;/script&gt;



	</content>
</entry>



<entry>
	<title>Sizes=&quot;auto&quot; pretty much requires width and height attributes</title>
	<link rel="alternate" href="https://ericportis.com/posts/2023/auto-sizes-pretty-much-requires-width-and-height/" />
	<id>tag:ericportis.com,2023-12-05:/posts/2023/auto-sizes-pretty-much-requires-width-and-height/</id>
	<updated>2023-12-05T00:00:00-08:00</updated>
	<content type="html">
	    &lt;p&gt;Writing, reading, and maintaining &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; attributes is the worst part about authoring responsive images; &lt;a href=&quot;https://html.spec.whatwg.org/multipage/images.html#:~:text=In%20this%20case%2C%20the%20sizes%20attribute%20can%20use%20the%20auto%20keyword&quot;&gt;auto-&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; for lazy-loaded images&lt;/a&gt; is a fantastic addition to the platform.&lt;/p&gt;

&lt;p&gt;(It is very funny that the &lt;a href=&quot;https://chromestatus.com/feature/5191555708616704&quot;&gt;Chrome Platform Status entry for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt;&lt;/a&gt; currently describes web developers’ collective opinion about the feature as “no signals.” &lt;a href=&quot;https://almanac.httparchive.org/en/2022/media#:~:text=The%20lazysizes.js%20library%20has%20already%20proven%20the%20appetite%20for%20this%20sort%20of%20solution&quot;&gt;One-in-ten &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes&lt;/code&gt; attributes on the web are &lt;em&gt;already using&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto&lt;/code&gt;,&lt;/a&gt; via &lt;a href=&quot;https://github.com/aFarkas/lazysizes&quot;&gt;lazysizes.js&lt;/a&gt;! But I digress.)&lt;/p&gt;

&lt;p&gt;Auto-sizes just shipped behind the &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/chrome-flags/#two_other_ways_to_try_out_experimental_features&quot;&gt;Experimental Web Platform Features flag&lt;/a&gt; in &lt;a href=&quot;https://www.google.com/chrome/canary/&quot;&gt;Chrome Canary&lt;/a&gt;. Which makes this a good time to try to explain the one weird thing about it. In short: in addition to requiring &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loading=lazy&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; also &lt;em&gt;basically&lt;/em&gt; requires that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;But don’t take my word for it. Here’s a note from &lt;a href=&quot;https://html.spec.whatwg.org/multipage/images.html#sizes-attributes&quot;&gt;Section 4.8.2.2 of the HTML specification, &lt;cite&gt;Sizes attributes&lt;/cite&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
Note: […] it is strongly encouraged to specify dimensions using the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-width&quot;&gt;&lt;code&gt;width&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height&quot;&gt;&lt;code&gt;height&lt;/code&gt;&lt;/a&gt; attributes or with CSS. Without specified dimensions, the image will likely render with 300x150 dimensions because &lt;code&gt;sizes=&quot;auto&quot;&lt;/code&gt; implies &lt;code&gt;contain-intrinsic-size: 300px 150px&lt;/code&gt;
&lt;/blockquote&gt;

&lt;p&gt;What!? 300×150? It’s true! &lt;a href=&quot;https://codepen.io/eeeps/full/yLZGKGZ&quot;&gt;Here’s an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element that uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;img
	loading=&quot;lazy&quot;
	sizes=&quot;auto&quot;
	srcset=&quot;
		https://o.img.rodeo/w_1200,h_1200,b_tomato/t_WxH/_.png   1200w,
		https://o.img.rodeo/w_900,h_900,b_goldenrod/t_WxH/_.png   900w,
		https://o.img.rodeo/w_600,h_600,b_forestgreen/t_WxH/_.png 600w,
		https://o.img.rodeo/w_300,h_300,b_dodgerblue/t_WxH/_.png  300w&quot;
	src=&quot;https://o.img.rodeo/w_300,h_300,b_forestgreen/t_WxH/_.png&quot;
	alt=&quot;An example image that reports its natural dimensions&quot;
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s what that looks like right now, in Canary, with the Experimental Web Platform Features flag on:&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&quot;https://o.img.rodeo/q_auto,f_auto/aqrmm7pegoz2bkuhhjuy.png&quot; alt=&quot;Screenshot of a Chrome window, showing a Codepen titled “sizes=auto, without width and height”. It’s a mostly blank page, showing a small green rectangle (the img), which has some squished-looking text reading “600x600” in it.&quot; /&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;Note that Canary &lt;em&gt;did&lt;/em&gt; load the best resource to fit (300&lt;code&gt;px&lt;/code&gt;@2x = 600 device pixels).&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Before I answer some obvious questions (what is happening, why), let’s tackle the practical: how do I fix it?&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;What values should I use for &lt;code&gt;height&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt;? &lt;/h2&gt;

&lt;p&gt;You should use the height and width, in pixels, of the largest resource that your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; can serve. If you’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;srcset&lt;/code&gt;, that means the dimensions of the largest resource in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;srcset&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;img
	loading=&quot;lazy&quot;
	sizes=&quot;auto&quot;
	width=&quot;1200&quot;
	height=&quot;1200&quot;
	srcset=&quot;
		https://o.img.rodeo/w_1200,h_1200,b_tomato/t_WxH/_.png   1200w,
		https://o.img.rodeo/w_900,h_900,b_goldenrod/t_WxH/_.png   900w,
		https://o.img.rodeo/w_600,h_600,b_forestgreen/t_WxH/_.png 600w,
		https://o.img.rodeo/w_300,h_300,b_dodgerblue/t_WxH/_.png  300w&quot;
	src=&quot;https://o.img.rodeo/w_300,h_300,b_dodgerblue/t_WxH/_.png&quot;
	alt=&quot;An example image that reports its natural dimensions&quot;
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(If you’re using &lt;a href=&quot;https://wicg.github.io/responsive-image-client-hints/&quot;&gt;Client Hints&lt;/a&gt;, you should use the dimensions of the full-size resource, before any server-side downscaling for delivery. &lt;a href=&quot;https://cdpn.io/pen/debug/PoVxMKX&quot;&gt;Here’s a (Chrome-only) example that uses Client Hints&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Depending on what else you have going on in your CSS, adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; to your HTML might unexpectedly distort your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, giving it a garish, fixed height which will pair terribly with its delightfully flexible width. One line of CSS will fix that for you:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;img {
	height: auto; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://codepen.io/eeeps/pen/YzBdaBZ&quot;&gt;All together, now&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt;
img {
	width: 100%;
	height: auto;
}
&amp;lt;/style&amp;gt;

&amp;lt;img
	loading=&quot;lazy&quot;
	sizes=&quot;auto&quot;
	width=&quot;1200&quot;
	height=&quot;1200&quot;
	srcset=&quot;
		https://o.img.rodeo/w_1200,h_1200,b_tomato/t_WxH/_.png   1200w,
		https://o.img.rodeo/w_900,h_900,b_goldenrod/t_WxH/_.png   900w,
		https://o.img.rodeo/w_600,h_600,b_forestgreen/t_WxH/_.png 600w,
		https://o.img.rodeo/w_300,h_300,b_dodgerblue/t_WxH/_.png  300w&quot;
	src=&quot;https://o.img.rodeo/w_300,h_300,b_dodgerblue/t_WxH/_.png&quot;
	alt=&quot;An example image that reports its natural dimensions&quot;
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s a screenshot of what that looks like in Canary with the flag on:&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&quot;https://o.img.rodeo/q_auto,f_auto/bizjuedsjbu2hqnghtyr.png&quot; alt=&quot;Screenshot of a Chrome window, showing a different Codepen, titled “sizes=auto, with width, height, and…” (unfortunately the title is truncated!?). Anyways in this one the img is gold/yellow, square, and fills almost the whole viewport. Inside if it is not-squished, normal-looking text, reading “900x900”.&quot; /&gt;
&lt;figcaption class=&quot;info-line center&quot;&gt;
All better!
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Perhaps you’re already adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt; to your responsive image HTML and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height: auto&lt;/code&gt; to your CSS in order to &lt;a href=&quot;https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/&quot;&gt;prevent layout shifts&lt;/a&gt;. Great! Change nothing!&lt;/p&gt;

&lt;p&gt;Ok, so:&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;What is happening and why?&lt;/h2&gt;

&lt;p&gt;By default, images on the web are rendered at their &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalWidth&quot;&gt;natural (&lt;span class=&quot;small-caps&quot;&gt;aka&lt;/span&gt; intrinsic)&lt;/a&gt; dimensions. &lt;a href=&quot;https://codepen.io/eeeps/pen/dyawmyb&quot;&gt;If you stick a 300×300 resource into an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and do nothing else, its layout size will be 300×300&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://codepen.io/eeeps/pen/MWLZOMM&quot;&gt;The natural dimensions of responsive images get surprisingly weird, fast&lt;/a&gt;, but:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;There are a number of ways that a loaded resource’s natural dimensions can affect the layout dimensions of its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;The value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; updates whenever an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;’s layout dimensions change, possibly triggering a new resource load.&lt;/li&gt;
  &lt;li&gt;A new resource will have new natural dimensions, which can affect the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;’s layout dimensions, which will update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; value, which may trigger a new resource load; the new resource will have new natural dimensions, which &lt;em&gt;…&amp;amp;c.&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
&lt;img src=&quot;https://o.img.rodeo/q_auto,f_auto/qjyxub5iy0w43ntmcowt.png&quot; alt=&quot;An illustration of the endless cycle. Three items are arranged in a circle. The top, centered one says “measured layout width”. There’s an arrow pointing from it to the second item in the bottom right, “sizes=auto”. Then there’s an arrow pointing from that to the third item, in the bottom left, “resource selection”. This one has three little icons signifying the different sizes of available images in light gray above it. Lastly, there’s another arrow, from “resource selection” back to “measured layout width” at the top.&quot; /&gt;
&lt;figcaption class=&quot;info-line&quot; style=&quot;margin-top: 0.5em; line-height: 1.2&quot;&gt;A lot of the spec discussion around &lt;code&gt;sizes=auto&lt;/code&gt; was just: people coming up with &lt;a href=&quot;https://en.wikipedia.org/wiki/Corner_case&quot;&gt;corner cases&lt;/a&gt; that would load &lt;em&gt;every single resource in a &lt;code&gt;srcset&lt;/code&gt;,&lt;/em&gt; one-after-the-other, using this mechanism.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://mastodon.social/@zcorpan&quot;&gt;Simon Pieters&lt;/a&gt;, who wrote the auto-sizes spec, made a few valiant and clever attempts to define all of the cases that could possibly trigger cyclical dependencies and work around them in ways that wouldn’t be too weird or noticeable for authors. But when the rubber hit the road and implementers started implementing, those workarounds &lt;a href=&quot;https://github.com/whatwg/html/issues/9448&quot;&gt;didn’t hold up&lt;/a&gt;. Ultimately, ’twas decided that the only way forward was to make a clean cut: mandate that a loaded resource’s natural dimensions &lt;em&gt;cannot&lt;/em&gt; affect its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;’s layout in any way, &lt;em&gt;ever,&lt;/em&gt; when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; is involved. Luckily, we have a fairly new feature that explicitly does exactly that: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment#size_containment&quot;&gt;size containment&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&quot;https://o.img.rodeo/q_auto,f_auto/bwnojfauuregfj4drh0b.png&quot; alt=&quot;The exact same diagram as before, except some red scissors have appeared, which are cutting the arrow between “resource selection” to “measured layout width”.&quot; /&gt;
&lt;figcaption class=&quot;info-line center&quot; style=&quot;margin-top: 0.5em&quot;&gt;Snip snip!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So now, this is in Chrome Canary’s UA stylesheet:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;img:is([sizes=&quot;auto&quot; i], [sizes^=&quot;auto,&quot; i]) {
    contain: size !important;
    contain-intrinsic-size: 300px 150px;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That says: are we dealing with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt;? Then its natural dimensions are 300×150. NO EXCEPTIONS.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/whatwg/html/issues/9448#issuecomment-1625060436&quot;&gt;300×150 was chosen&lt;/a&gt; because that’s what &lt;a href=&quot;https://html.spec.whatwg.org/multipage/media.html#the-video-element:default-object-size&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://html.spec.whatwg.org/multipage/canvas.html#the-canvas-element:~:text=The%20width%20attribute%20defaults%20to%20300%2C%20and%20the%20height%20attribute%20defaults%20to%20150.&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;&lt;/a&gt; do; all of these elements use this smallish-but-non-zero default size in order to encourage you to do better.&lt;/p&gt;

&lt;p&gt;Finally, we get to the load-bearing weasel-words in the title of this post: “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt; &lt;em&gt;pretty much&lt;/em&gt; requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; attributes”. &lt;a href=&quot;https://front-end.social/@eeeps/111530259043483206&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; are not the only way!&lt;/a&gt; I think the pattern that I’m pushing, where you describe the aspect ratio of your content in HTML, and give it a flexible size in CSS – provides a better separation of concerns than any other solution. But the fundamental takeaway is: when you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizes=auto&lt;/code&gt;, your image will be assigned a natural size of 300×150 and a natural aspect ratio of 2:1, and it’s on you to override those dimensions.&lt;/p&gt;

	</content>
</entry>



<entry>
	<title>The Maintenance Race (and me)</title>
	<link rel="alternate" href="https://ericportis.com/posts/2023/the-maintenance-race-and-me/" />
	<id>tag:ericportis.com,2023-10-04:/posts/2023/the-maintenance-race-and-me/</id>
	<updated>2023-10-04T00:00:00-07:00</updated>
	<content type="html">
	    &lt;p&gt;A couple of days ago, &lt;a href=&quot;https://mastodon.social/@davatron5000/111172614117755841&quot;&gt;Dave Rupert shared&lt;/a&gt; an excellent article that I’ve been thinking about for, like, a whole year: Steward Brand’s &lt;cite&gt;&lt;a href=&quot;https://worksinprogress.co/issue/the-maintenance-race&quot;&gt;The Maintenance Race&lt;/a&gt;&lt;/cite&gt;.&lt;/p&gt;

&lt;p&gt;The article is about a round-the-world solo sailboat race held in 1968, and you should &lt;a href=&quot;https://worksinprogress.co/issue/the-maintenance-race&quot;&gt;go read it&lt;/a&gt; before you read any of this.&lt;/p&gt;

&lt;p&gt;After Dave shared it, &lt;a href=&quot;https://front-end.social/@eeeps/111173226921088911&quot;&gt;I sarcastically quipped back&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;a href=&quot;https://mastodon.social/@davatron5000/&quot;&gt;@davatron5000&lt;/a&gt; me, looking at a sink full of unwashed dishes and piles of unsorted mail on the counter, before retiring to the couch: “every day a new boat”
&lt;/blockquote&gt;

&lt;p&gt;…who &lt;a href=&quot;https://mastodon.social/@davatron5000/111173492294038307&quot;&gt;responded in earnest&lt;/a&gt;…&lt;/p&gt;

&lt;blockquote&gt;
&lt;a href=&quot;https://front-end.social/@eeeps/&quot;&gt;@eeeps&lt;/a&gt; Yeah. Looking at my desk I felt the shame. I like the attitude tho and aspire to get there.
&lt;/blockquote&gt;

&lt;p&gt;…and made me realize that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;My pithy reply was cheap and insufficient.&lt;/li&gt;
  &lt;li&gt;My year-of-mulling-this-thing-over has left me with some thoughts that are &lt;em&gt;too big for toots.&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First: the whole premise of a solo around-the-world race represents something I am both deeply drawn to, and increasingly wary of.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot; id=&quot;men-sailing-alone&quot;&gt;Men sailing alone&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://austinkleon.com/2023/09/18/the-inner-state-of-the-average-man/&quot;&gt;Austin Kleon quoted James Hollis the other day&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I have suggested that women look at men this way: if they took away their own network of intimate friends, […] concluded that they were almost wholly alone in the world, and understood that they would be defined only by standards of productivity external to them, they would then know the inner state of the average man. They are horrified at this notion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Through this lens, the Golden Globe Race was the &lt;em&gt;ideal man scenario&lt;/em&gt;: each competitor was completely alone, in complete control of themselves and their tools, and placed in complete opposition to the vast and unforgiving world around them. They were in a competition where “success” and “failure” were externally defined and objectively measured.&lt;/p&gt;

&lt;p&gt;I feel the appeal of all of this &lt;em&gt;in my bones&lt;/em&gt;. But, like Austin, I am trying to fight it. I can see the person that this stance towards the world would lead (and, in some ways, has already led) me to become – and I don’t like him! I want to make more of my own meaning, and find – and share! – that meaning in community.&lt;/p&gt;

&lt;p&gt;Moitessier, the hero of the story, &lt;em&gt;does&lt;/em&gt; end up finding his own meaning: both in the journey (love this) and in being alone (oh no). It’s beautiful, but it’s not who I want to be.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;The old ways are best&lt;/h2&gt;

&lt;p&gt;So, toxic/solitary masculinity is the setting for our story. Within it, three protagonists attempt to build and maintain systems in order to achieve their goals. And hey, aren’t we all just out here, attempting to build and maintain systems in order to achieve our goals, in one way or another? Here’s how they fared:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The one who relied on schmantzy tech but had no interest-in/discipline-for the fundamentals of sailing began lying about his progress, went mad, and ultimately died (this is React).&lt;/li&gt;
  &lt;li&gt;The one with a ramshackle, roll-with-the-punches, can-do attitude won the race (this is PHP).&lt;/li&gt;
  &lt;li&gt;Finally, there was one sailor who pursued simplicity above all else. He stuck to the basics and perfected the fundamentals. He double-checked and triple-reinforced everything before setting off. And then he found a serene monk-like meaning in maintenance and routine. What happened to him? Well, he just kept on sailing, on and on and on (this is me, reciting passages from the &lt;a href=&quot;https://alistapart.com/article/dao/&quot;&gt;Dao&lt;/a&gt;, floating an inch above my office chair, exuding &lt;a href=&quot;https://html.energy&quot;&gt;HTML Energy&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clearly, there are value systems at play here that I am deeply attracted and attached to. I love seeing my mindful-minimalist guy become the transcendent hero. I love (the idea of) prepared, focused discipline. Less is more! Choose boring technology! Perhaps most of all I love a story that richly illustrates the superiority of my technological sensibilities using historical boats.&lt;/p&gt;

&lt;p&gt;But.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;Inbox zero for boats&lt;/h2&gt;

&lt;p&gt;Back to me in the kitchen. Here’s the passage I referenced in my quip to Dave, and often reflect on, when I find myself half-assing maintenance of various sorts:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One time, when I remarked on how fit [Moitessier’s] boat looked, he said, ‘My rule is, a new boat every day’. His years at sea had taught him that if you don’t fix something when you first see it beginning to fail, it is very likely to finish failing just when it is the most dangerous and the hardest to deal with, such as in the midst of a storm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I look at my life, and “a new boat every day,” applied broadly, is untenable. There are too many systems; there isn’t enough time to maintain everything to 100% every single day. Some days I look around and all I see are undone tasks; aspirations half-met, things in various states of disrepair. So why don’t I ruthlessly simplify, like Moitessier?&lt;/p&gt;

&lt;p&gt;I guess it’s because Moitessier was living a life that had been maximally-scoped-down: it was just him, his boat, and the sailing.&lt;/p&gt;

&lt;p&gt;I’ve got, like, more than that going on? And my life is richer for it. My life has other people in it, and I’m doing all sorts of stuff – changing my mind, trying new things, negotiating, compromising – and there’s a variety and &lt;em&gt;opportunity&lt;/em&gt; to the resulting, sometimes messy, push-and-pull that I wouldn’t trade for the serenity of a deep-cleaned kitchen every morning.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/20230901170042/https://www.washingtonpost.com/home/2023/01/26/marie-kondo-kurashi-inner-calm/&quot;&gt;Like Marie Kondo herself said&lt;/a&gt;, not too long ago:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;My home is messy, but the way I am spending my time is the right way for me at this time at this stage of my life.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There have been (and still are!) spaces, places, and seasons in my life for reduction; for focus; for discipline. You gotta pick your boats, though. Maybe your inbox is one; maybe your backlog is one; maybe your kitchen is a boat.&lt;/p&gt;

&lt;p&gt;But a whole entire life lived so rigidly? Not for me. Too limiting. Gotta find a balance.&lt;/p&gt;

	</content>
</entry>



<entry>
	<title>View Transitions Break Incremental Rendering</title>
	<link rel="alternate" href="https://ericportis.com/posts/2023/view-transitions-break-incremental-rendering/" />
	<id>tag:ericportis.com,2023-08-22:/posts/2023/view-transitions-break-incremental-rendering/</id>
	<updated>2023-08-22T00:00:00-07:00</updated>
	<content type="html">
	    &lt;style&gt;
.observablehq svg {
	background-color: unset;
}
.observablehq {
	margin-top: 2em;
}
&lt;/style&gt;

&lt;p&gt;By default, webpages render like this:&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&quot;/assets/2023-08-22-view-transitions-break-incremental-rendering/fcp.png&quot; /&gt;
&lt;figcaption&gt;
&lt;p class=&quot;info-line&quot;&gt;Image swiped from &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;Phil Walton’s web.dev article on First Contentful Paint&lt;/a&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Stuff pops in as it’s loaded, parsed, and styled. This is called &lt;a href=&quot;https://en.wikipedia.org/wiki/Incremental_rendering&quot;&gt;incremental rendering&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Incremental rendering is cool and good. It is &lt;em&gt;especially&lt;/em&gt; cool/good for people on slow connections. They get to see pieces of the page ASAP; incremental rendering shows them &lt;em&gt;something&lt;/em&gt; while the page is in flight, instead of nothing.&lt;/p&gt;

&lt;figure&gt;
&lt;div id=&quot;observablehq-chart-66f94a4d&quot;&gt;&lt;/div&gt;
&lt;figcaption&gt;
&lt;p class=&quot;info-line&quot;&gt;&lt;a href=&quot;https://observablehq.com/d/d1ae3f2b705db7e3@237&quot;&gt;First Contentful Paint and Page Load Time distributions from the June 2023 RUM Archive&lt;/a&gt;, on &lt;a href=&quot;https://observablehq.com&quot;&gt;Observable&lt;/a&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/npm/@observablehq/inspector@5/dist/inspector.css&quot; /&gt;
&lt;script type=&quot;module&quot;&gt;
import {Runtime, Inspector} from &quot;https://cdn.jsdelivr.net/npm/@observablehq/runtime@5/dist/runtime.js&quot;;
import define from &quot;https://api.observablehq.com/d/d1ae3f2b705db7e3@237.js?v=3&quot;;
new Runtime().module(define, name =&gt; {
  if (name === &quot;chart&quot;) return new Inspector(document.querySelector(&quot;#observablehq-chart-66f94a4d&quot;));
});
&lt;/script&gt;
&lt;/figure&gt;

&lt;p&gt;For the 99th percentile of page loads in a recent batch of &lt;a href=&quot;https://rumarchive.com/&quot;&gt;RUM Archive&lt;/a&gt; data, incremental rendering gave folks &lt;em&gt;something&lt;/em&gt; after 10 seconds. Without it, they would have spent 40 seconds looking at nothing.&lt;sup&gt;&lt;a href=&quot;#footnote-1&quot; id=&quot;footnote-mark-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;While incremental rendering is especially helpful for people in slower contexts, we don’t have to look to the 99th percentile to see tangible benefits. For the median page load, incremental rendering halved the time people waited to see something, cutting it from just over two seconds to just over one.&lt;/p&gt;

&lt;p&gt;So that’s incremental rendering. I like it a lot.&lt;/p&gt;

&lt;p&gt;Cross-document &lt;a href=&quot;https://drafts.csswg.org/css-view-transitions-1/&quot;&gt;View Transitions&lt;/a&gt; render like this:&lt;/p&gt;

&lt;figure&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; src=&quot;/assets/2023-08-22-view-transitions-break-incremental-rendering/daverupert-viewtransitions.mp4&quot;&gt;&lt;/video&gt;
&lt;figcaption&gt;
&lt;p class=&quot;info-line&quot;&gt;Video swiped from &lt;a href=&quot;https://daverupert.com/2023/05/getting-started-view-transitions/&quot;&gt;Dave Rupert’s &lt;cite&gt;Getting started with View Transitions on multi-page apps&lt;/cite&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Upon clicking a link, we leave one fully-rendered page, and usher in a new fully-rendered page, in an animated/orchestrated manner.&lt;/p&gt;

&lt;p&gt;I ❤️ View Transitions. Being able to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;establish object permanence across navigations, and&lt;/li&gt;
  &lt;li&gt;direct attention with motion in order to highlight important arriving/departing elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…is going to give designers the ability to make websites easier to navigate and understand. We’re going to be able to establish freaking &lt;a href=&quot;https://developer.apple.com/tutorials/app-dev-training/creating-a-navigation-hierarchy&quot;&gt;spatial hierarchies across pages&lt;/a&gt;, on regular websites! Death to jarring, abrupt transitions!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://adactio.com/journal/19578&quot;&gt;Death&lt;/a&gt; to &lt;a href=&quot;https://www.zachleat.com/web/single-page-applications/&quot;&gt;single-page-apps&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;And, uh, death to — incremental rendering? (oh no).&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;The problem&lt;/h2&gt;

&lt;p&gt;Consider a View Transition to a partially-rendered page.&lt;/p&gt;

&lt;p&gt;It might look fine, sometimes. For instance, if an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img src&amp;gt;&lt;/code&gt; hasn’t loaded, but that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element is otherwise all parsed and styled &lt;em&gt;and&lt;/em&gt; includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt; attributes, its dimensions will be reserved on the layout. We’ll get a transition to a solid &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt; placeholder or something, which is not the end of the world. A not-loaded-yet font feels similarly not-horrible: we might View Transition in some invisible FOIT text, which, worst-case, triggers a reflow after it loads, jostling  surrounding elements some time later. We lose the transition effect, but it’s kinda-sorta falling back to the experience you would have had, without the View Transition.&lt;/p&gt;

&lt;p&gt;But. If we initiate a View Transition before things that are producing DOM or layout are loaded and parsed – CSS, HTML, and JS – that could be a &lt;em&gt;disaster&lt;/em&gt;. Let’s say a bit of DOM hasn’t been loaded/parsed/styled/rendered yet when a View Transition starts, and then sometime later, when that piece of DOM &lt;em&gt;is&lt;/em&gt; ready to render, it ends up having the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;view-transition-name&lt;/code&gt; as something on the outgoing page. This means that the author &lt;em&gt;intended&lt;/em&gt; for the old element to transition smoothly into the new one. Instead, the old one exits the page, and then the new one pops in, sometime later, not part of the transition process at all.&lt;/p&gt;

&lt;p&gt;This is bad. At best it’ll look glitchy. At worst it does the exact &lt;em&gt;opposite&lt;/em&gt; of guiding the eye to the most important elements on the page, and establishing object permanence. Instead of showing users that this thing is the same as that thing, we’ve told them, &lt;em&gt;perhaps with extra motion/panache&lt;/em&gt;, that even though these the two things might seem the same, they are in fact distinct. It’s like shaking your head “no” while telling someone “yes.” Or perhaps it is more like walking out of the room and then teleporting back into place before you even answer their question. It is disorienting, surprising, and unsettling. It is much worse than having no transition at all.&lt;/p&gt;

&lt;p&gt;This is a fundamental problem with View Transitions. And because browsers can and regularly do render partial pages – even on fast machines with fast connections loading fast pages – it’s going to affect &lt;em&gt;everyone&lt;/em&gt;.&lt;/p&gt;

&lt;h2 class=&quot;little-h&quot;&gt;How to solve it?&lt;/h2&gt;

&lt;h3 class=&quot;tiny-h&quot;&gt;Paint slower&lt;/h3&gt;

&lt;p&gt;The folks prototyping cross-document View Transitions in Chrome are solving this fundamental problem by building and extending features which &lt;em&gt;delay the first paint until the page is ready to transition.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Already spec’d and implemented: &lt;a href=&quot;https://chromestatus.com/feature/5452774595624960&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blocking=render&lt;/code&gt; on scripts and stylesheets&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;A proposed extension: &lt;a href=&quot;https://github.com/WICG/view-transitions/blob/main/document-render-blocking.md&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blocking=render&lt;/code&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;html&amp;gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two important points about these:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;In order to use View Transitions, you are going to have to use these features (no matter how fast your site is).&lt;/li&gt;
  &lt;li&gt;You can use these features to disable incremental rendering, even when you’re &lt;em&gt;not&lt;/em&gt; using View Transitions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They are not tied to View Transitions because they are also supposed to solve the other (&lt;abbr&gt;IMO&lt;/abbr&gt; all bad) use-cases people have for disabling incremental rendering:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.google.com/optimize/answer/7100284?hl=en&quot;&gt;“I’m doing a client-side A/B test”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2021/06/how-to-fix-cumulative-layout-shift-issues/#synchronous-javascript&quot;&gt;“I’m trying to fix a bad CLS score“&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/TimVereecke/status/1558426328659001344&quot;&gt;“I don’t like it”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re at the 25th percentile in that chart at the top of the post – experiencing a First Contentful Paint at ~600ms, with a total Page Load Time of ~1100ms – trading an extra half-second of time-spent-staring-at-nothing in exchange for a less &lt;a href=&quot;https://daverupert.com/2015/10/on-hurk-jerk/&quot;&gt;hurk-jerk-y&lt;/a&gt; experience might seem like an improvement.&lt;/p&gt;

&lt;p&gt;But if you’re in the 99th percentile, you would never choose to stare at nothing for forty seconds when something — please, anything! — could have appeared after ten.&lt;/p&gt;

&lt;p&gt;I worry that giving developers tools to explicitly block render – with or without View Transitions – is going to make experiencing the web on slow connections and cheap devices &lt;em&gt;much&lt;/em&gt; worse. Because while the web’s authors generally experience the web at the left side of our chart, and make decisions based on those experiences, significant portions of the web’s users live their lives over on the right.&lt;/p&gt;

&lt;h3 class=&quot;tiny-h&quot;&gt;But not too slowly&lt;/h3&gt;

&lt;p&gt;The render-blocking specs have made room for implementation-defined timeouts for render-blocking features (&lt;a href=&quot;https://developer.chrome.com/blog/font-display/#block&quot;&gt;&lt;em&gt;ala&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-display: block&lt;/code&gt;&lt;/a&gt;), and there is &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/9155&quot;&gt;currently an open discussion on how/whether to bail out of View Transitions when things are taking too long&lt;/a&gt;. &lt;em&gt;I think this is vital.&lt;/em&gt; We need tuned timeouts that ensure that the long tail of slow devices/connections don’t wait for View Transitions if they would excessively delay first paint.&lt;/p&gt;

&lt;p&gt;It’s probably good that this timeout is left to implementers to define but it’s not great that Chrome currently sets it to network timeout which I think is, like, 240 seconds!? I fully expect this all to get better as View Transitions moves towards shipping, though.&lt;/p&gt;

&lt;h3 class=&quot;tiny-h&quot;&gt;Paint just slowly enough?&lt;/h3&gt;

&lt;p&gt;The specs also try to give authors tools to tell the browser a bare-minimum amount of &lt;abbr&gt;HTML&lt;/abbr&gt;, &lt;abbr&gt;CSS&lt;/abbr&gt;, and &lt;abbr&gt;JS&lt;/abbr&gt; that’s needed to paint the initial viewport, but that’s &lt;a href=&quot;https://csswizardry.com/2022/09/critical-css-not-so-fast/&quot;&gt;incredibly hard to do well&lt;/a&gt;. I fully expect everybody who wants to use View Transitions – especially toolmakers and template/component authors who don’t know much about the content they’re working with or the context they’re working in — will simply slap &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blocking=render&lt;/code&gt; onto everything.&lt;/p&gt;

&lt;h3 class=&quot;tiny-h&quot;&gt;Paint… faster?&lt;/h3&gt;

&lt;p&gt;I have low expectations, but sincere hope, that we can do better. Maybe instead of significantly delaying the first paint for View Transitions, quick render-blocking timeouts could serve as a stick which, paired with the carrot of View Transitions, might motivate authors to push their pages to paint &lt;em&gt;faster&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Besides quick timeouts, what else could View Transitions and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blocking=render&lt;/code&gt; do, here? What other platform-level changes could make faster and more-complete first paints &lt;em&gt;hard to screw up,&lt;/em&gt; rather than &lt;em&gt;hard to do at all&lt;/em&gt; – &lt;em&gt;especially&lt;/em&gt; on slower connections and devices? I don’t know but I want to spend some time thinking about it.&lt;/p&gt;

&lt;p&gt;Low expectations. High hopes!&lt;/p&gt;

&lt;hr class=&quot;footnote-hr&quot; /&gt;

&lt;p class=&quot;info-line&quot;&gt;Thanks to &lt;a href=&quot;https://hire.wil.to&quot;&gt;Mat Marquis&lt;/a&gt;, &lt;a href=&quot;https://www.kryogenix.org&quot;&gt;Stuart Langridge&lt;/a&gt;, and &lt;a href=&quot;https://cloudfour.com/is/jason-grigsby/&quot;&gt;Jason Grigsby&lt;/a&gt; for reviewing an early draft!&lt;/p&gt;

&lt;ol class=&quot;footnotes fit-to-p-width info-line&quot;&gt;

	&lt;li id=&quot;footnote-1&quot;&gt;
&lt;p&gt;Important caveat! For this analysis, I used two metrics: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event&quot;&gt;Page Load Time&lt;/a&gt; and &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint&lt;/a&gt;. (I also assume they are perfectly correlated, but let’s leave that aside; bigger fish to fry). First Contentful Paint is the exact metric we need; Page Load Time isn’t. The proposed features would delay first paint until the document and many scripts and stylesheets have been loaded. Page Load Time, on the other hand, measures how long it took for &lt;em&gt;everything&lt;/em&gt; to load, notably including images (which the proposed features would not wait for). So this chart likely overstates the case. I would love to get a more accurate picture of the potential impact of the proposals; alas this is the best I know how to do at the moment.
		&lt;a href=&quot;#footnote-mark-1&quot; class=&quot;footnote-return-link&quot; title=&quot;return to text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
&lt;/ol&gt;


	</content>
</entry>



</feed>
