<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jeremy Robert Jones’ Blog</title>
	<subtitle>A feed of the latest posts from my blog.</subtitle>
	<link href="https://jeremyrobertjones.com/feed.xml" rel="self"/>
	<link href="https://jeremyrobertjones.com/"/>
	<updated>2025-08-01T00:00:00Z</updated>
	<id>https://jeremyrobertjones.com</id>
	<author>
    <name>Jeremy Robert Jones</name>
    <email>minttoothpick@protonmail.com</email>
	</author>
	
    
    <entry>
      <title>On being an unexpectedly unemployed generalist</title>
      <link href="https://jeremyrobertjones.com/blog/unexpectedly-unemployed-generalist/"/>
      
      <updated>2025-08-01T00:00:00Z</updated>
      <id>https://jeremyrobertjones.com/blog/unexpectedly-unemployed-generalist/</id>
      <content type="html"><![CDATA[
        <p>I sure didn't expect to be unemployed this year. For a decade I worked as a web manager and developer for the U.S. Forest Service's Research &amp; Development branch. I supported scientists and communications teams in sharing their work with land managers, researchers, extension forestry, and the general public. This was research grounded in real-world needs: how to manage pests and diseases, how to adapt forests to climate change, how to sustain timber production and forest health in a changing landscape. I loved the mission, the people, and the work. I was beyond pleased to consider this the job from which I would retire. I was already planning to ask for a vegan ice cream cake at my farewell party in 25 years.</p>
<p>In hindsight, that optimism feels a bit misplaced. There was a presidential election, and forces were already gathering—intent on dismantling the civil service, deprioritizing climate work, and reshaping the government itself. I should have taken the Project 2025 initiative more seriously. It's a destructive and heavy-handed playbook, and one that I naively assumed would stay on the fringe.</p>
<p>My bad. I was not prepared for the speed and scope of what would happen after the presidential inauguration in January. Nor for the outright hostility toward civil servants like me, cast as wasteful, resource-sucking bureaucrats and demonized as obstacles to some imagined better version of America. (See also: <a href="https://www.propublica.org/article/video-donald-trump-russ-vought-center-renewing-america-maga">Russell Vought</a>, Director of the Office of Management and Budget, saying that he wants to put us &quot;in trauma&quot;; and Georgia Representative <a href="https://thehill.com/homenews/house/5165176-marjorie-taylor-greene-criticized/">Marjorie Taylor Greene</a> claiming we are undeserving of our jobs and paychecks.)</p>
<p>I admit I didn't fully grasp the trajectory we were on. The seemingly sudden upwelling of negative sentiment and villainization was disorienting and distressing.</p>
<p>Then DOGE began sending out shady-looking emails addressed from the Office of Personnel Management, dangling a resignation offer: voluntarily quit within the next week, and continue to receive pay and benefits through September. They flippantly pitched it like a cruise brochure: I could take this sweet buyout and &quot;go on my dream vacation.&quot;</p>
<p>I ignored that, and carried on for another two months, increasingly fatigued by the mandate to return to in-person work at an office staffed with people outside my own unit, murmurings of mandatory relocations, layoffs, and downsizings. When a new resignation offer came through in April, it felt less like a choice to be considered than the only lifeline available to me at the time.</p>
<p>So I signed the deferred resignation agreement. On paper, it was an objectively good deal: five months to figure out my next move. Technically, I am still employed, but I'm not working—Schrödinger's job status. But I grieved that decision. I loved my job, and I believed in the work we did.</p>
<p>And now here I am, three months later, still grappling with my loss of direction, and unsure of whether I made the best decision. Feeling fully unmoored, adrift in the job market, trying to reassemble a career-relevant identity.</p>
<p>Because, first off, I'm not sure what to call myself. Over the years, my work evolved so frequently that most tidy job titles feel like a poor fit. I've done many things, often learning them as I went. I tried to bridge gaps between teams and look for areas where we could improve processes.</p>
<p>Web developer? Yeah, I've completed full website redesigns from the ground up, created WordPress themes, used PHP to integrate with databases. Until my team pivoted to focusing on video production for a couple years, during which time I practiced writing scripts, filming in the field, editing, motion graphics, and even writing and recording audio descriptions for accessibility. Then there were dozens of hybrid meetings I helped produce, from livestreaming and equipment setup to post-production. I've trained contributors in our CMS, written content strategy documents, maintained databases, painstakingly remediated ancient PDFs for Section 508 compliance. I've built automated project request tracking systems and gone bleary-eyed scouring Google Analytics for worthwhile metrics to guide content and design decisions. And on and on.</p>
<p>I'm a generalist. Not because I can't go deep, but because the work continually shifted and the priorities changed. I can recognize my adaptability as a strength, but how do I convey that strength on a job application? I know that beneath my shifting roles there is a consistent set of strengths:</p>
<ul>
<li>A detail-oriented eye for clarity, consistency, and quality</li>
<li>Supporting cross-functional teams with empathy and precision</li>
<li>Prioritizing accessibility, equity, and usefulness</li>
<li>Translating complex ideas into something people can use and understand</li>
<li>Identifying, implementing, and documenting process improvements</li>
</ul>
<p>I guess that's part of why I'm writing this—to remind myself of what I bring to the table, and to make sense of where I go from here. I'm reflecting, asking questions, hoping for answers, practicing openness. And while I worry that this whole thing is self-indulgent, that's okay because this is my blog.</p>
<p>I want to be part of a team where my technical and my soft skills matter. Where I can help people do meaningful work. I do believe there are opportunities for generalists who are attentive, step in where needed, and help make things happen.</p>
<p>So here's to the moments when the unknown feels less like a void, and more like an opening to whatever's next.</p>

      ]]></content>
    </entry>
	
    
    <entry>
      <title>Creating proportional, equal-height image rows with CSS, 11ty, and Nunjucks</title>
      <link href="https://jeremyrobertjones.com/blog/proportional-equal-height-image-row-css-11ty-nunjucks/"/>
      
      <updated>2025-06-15T00:00:00Z</updated>
      <id>https://jeremyrobertjones.com/blog/proportional-equal-height-image-row-css-11ty-nunjucks/</id>
      <content type="html"><![CDATA[
        <p>A while back I came across an unexpectedly challenging image layout issue in CSS. I wanted to create a fluid, flexible, responsive image row where:</p>
<ol>
<li>The images display in a single horizontal row without wrapping or overflowing,</li>
<li>The entire row width is fluid; it expands or shrinks to fill the width of its parent container,</li>
<li>Images with various aspect ratios are scaled proportionally to each other and together take up the full width of the row, and</li>
<li>All the images display at the same height, with their widths adjusting to maintain the aspect ratios.</li>
</ol>
<p>Like this:</p>
<div class="resizable">
<figure><div class="image-row">
      <div class="image-row__item" style="--aspect-ratio: 1.6506189821182944">
              <picture>
                <source type="image/avif" data-srcset="/images/fireworks-couch-300w.avif 300w, /images/fireworks-couch-600w.avif 600w, /images/fireworks-couch-900w.avif 900w, /images/fireworks-couch-1200w.avif 1200w" data-sizes="auto">
                <source type="image/webp" data-srcset="/images/fireworks-couch-300w.webp 300w, /images/fireworks-couch-600w.webp 600w, /images/fireworks-couch-900w.webp 900w, /images/fireworks-couch-1200w.webp 1200w" data-sizes="auto">
                <img src="/images/fireworks-couch-300w.jpeg"
                     data-srcset="/images/fireworks-couch-300w.jpeg 300w, /images/fireworks-couch-600w.jpeg 600w, /images/fireworks-couch-900w.jpeg 900w, /images/fireworks-couch-1200w.jpeg 1200w"
                     data-sizes="auto"
                     decoding="async"
                     class="lazyload"
                     alt="Alt text"
                >
              </picture>
            </div><div class="image-row__item" style="--aspect-ratio: 0.7504690431519699">
              <picture>
                <source type="image/avif" data-srcset="/images/sloop-300w.avif 300w, /images/sloop-600w.avif 600w, /images/sloop-900w.avif 900w, /images/sloop-1200w.avif 1200w" data-sizes="auto">
                <source type="image/webp" data-srcset="/images/sloop-300w.webp 300w, /images/sloop-600w.webp 600w, /images/sloop-900w.webp 900w, /images/sloop-1200w.webp 1200w" data-sizes="auto">
                <img src="/images/sloop-300w.jpeg"
                     data-srcset="/images/sloop-300w.jpeg 300w, /images/sloop-600w.jpeg 600w, /images/sloop-900w.jpeg 900w, /images/sloop-1200w.jpeg 1200w"
                     data-sizes="auto"
                     decoding="async"
                     class="lazyload"
                     alt="Alt text"
                >
              </picture>
            </div><div class="image-row__item" style="--aspect-ratio: 1">
              <picture>
                <source type="image/avif" data-srcset="/images/moontuck-300w.avif 300w, /images/moontuck-600w.avif 600w, /images/moontuck-900w.avif 900w, /images/moontuck-1200w.avif 1200w" data-sizes="auto">
                <source type="image/webp" data-srcset="/images/moontuck-300w.webp 300w, /images/moontuck-600w.webp 600w, /images/moontuck-900w.webp 900w, /images/moontuck-1200w.webp 1200w" data-sizes="auto">
                <img src="/images/moontuck-300w.jpeg"
                     data-srcset="/images/moontuck-300w.jpeg 300w, /images/moontuck-600w.jpeg 600w, /images/moontuck-900w.jpeg 900w, /images/moontuck-1200w.jpeg 1200w"
                     data-sizes="auto"
                     decoding="async"
                     class="lazyload"
                     alt="Alt text"
                >
              </picture>
            </div>
      </div>
      <figcaption class="text-small">Adjust the width to see these images fit together nicely.</figcaption>
    </figure>
</div>
<p>I thought this would be a <em>cinch</em> with CSS right out of the (flex)box, but after fiddling with combinations of <code>flex</code> and <code>object-fit</code> values, I realized it was less intuitive than I assumed.</p>
<p><a href="https://codepen.io/minttoothpick/pen/gbOvyvP">Here's a Codepen of the closest I got to a solution.</a> In this example, the images maintain their aspect ratios, but because I set an explicit <code>height</code> on each image, they don't fluidly expand to fill the row container.</p>
<p>So what's missing? It involves aspect ratios, of course!</p>
<h2>The solution: using aspect ratios as flex values</h2>
<p>Eventually, I found this <a href="https://codepen.io/blimpage/pen/obWdgp">Codepen by Pat McKenna</a> demonstrating a <a href="https://web.archive.org/web/20171029140328/https://kartikprabhu.com/articles/equal-height-images-flexbox">technique by Kartik Prabhu</a> that was the key to solving this puzzle. (The concept is also explained well by <a href="https://web.archive.org/web/20210510190400/https://olivermak.es/2017/01/fluid-grid/">Oliver Pattison in their article</a>.)</p>
<p>The key is <strong>using each image's aspect ratio as its parent container’s <code>flex</code> value</strong>:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.fluid-row > img</span> <span class="token punctuation">{</span>
  <span class="token property">flex</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>width / height<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Here's a basic implementation:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fluid-row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fluid-row__item<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--aspect-ratio</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>landscape.jpg<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Landscape image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fluid-row__item<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--aspect-ratio</span><span class="token punctuation">:</span> 0.8<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>portrait.jpg<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Portrait image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token comment">&lt;!-- more images... --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span></code></pre>
<pre class="language-css"><code class="language-css"><span class="token selector">.fluid-row</span> <span class="token punctuation">{</span>
  <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.fluid-row__item</span> <span class="token punctuation">{</span>
  <span class="token property">flex</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--aspect-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.fluid-row__item > img</span> <span class="token punctuation">{</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>See <a href="https://codepen.io/minttoothpick/pen/NPWwBBb">Equal-height flexible image row 2: solution</a> on Codepen.</p>
<aside style="background-color: var(--color-white-dark); padding: .9em; margin-inline: -.9em; border-radius: 3px;">
<p><strong>Whoops:</strong> My original version of this code did not include the <code>div.fluid-row__item</code> elements around each image. This worked in Firefox, but Chromium seems to let <code>img { width: 100%; }</code> override the <code>flex</code> value. Wrapping each image in a container and setting <code>flex</code> there takes care of this.</p>
</aside>
<h2>How the CSS works</h2>
<p>Why/how does this work? Lemme review some flexbox basics. The <code>flex</code> property controls how much an item will grow or shrink <em>relative to other items</em>. By setting it to the aspect ratio (width divided by height in this case), I'm telling the browser to allocate space proportionally to the width the image needs to maintain its aspect ratio at a consistent height.</p>
<p>For example:</p>
<ul>
<li>A square image (1:1 ratio) would get a flex value of 1.</li>
<li>A landscape image (e.g., 16:9 ratio) would get a flex value of 1.78.</li>
<li>A portrait image (e.g., 3:4 ratio) would get a flex value of 0.75.</li>
</ul>
<p>What's happening with the rule <code>flex: var(--aspect-ratio)</code>? Setting the shorthand <code>flex</code> rule with a single value expands it to this:</p>
<pre class="language-css"><code class="language-css"><span class="token property">flex-grow</span><span class="token punctuation">:</span> [aspect-ratio]<span class="token punctuation">;</span>
<span class="token property">flex-shrink</span><span class="token punctuation">:</span> [aspect-ratio]<span class="token punctuation">;</span>
<span class="token property">flex-basis</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span></code></pre>
<p>Here's a summary of what each rule does:</p>
<ul>
<li><code>flex-basis: 0%</code> tells the browser to start from zero width; don't consider the content or its intrinsic size.</li>
<li><code>flex-grow: [aspect-ratio]</code> tells the browser to grow this item proportionally to its aspect ratio. If one item has an aspect ratio of 2, and another has 1, the first one will take up twice as much horizontal space.</li>
<li><code>flex-shrink: [aspect-ratio]</code> - since <code>flex-basis</code> is 0% and the container isn't overflowing, this rule doesn't actually do anything in this case.</li>
</ul>
<h2>Making it responsive with lazysizes</h2>
<p>Okay, so this is nifty, but how the heck do I make it responsive?</p>
<p>Using <code>srcset</code> and <code>sizes</code> lets the browser choose the appropriate image size, but I need to know approximately what size the image will display at ahead of time so that I can set <code>sizes</code> appropriately.</p>
<p>With the flexbox technique I'm using, an image's width is dependent on the size of its neighbors, so I can't predict if a given image will be 50% of the viewport, or 10%, or any other value.</p>
<p>This is where the <a href="https://github.com/aFarkas/lazysizes"><strong>lazysizes</strong> JavaScript library</a> comes to the rescue. It offers a handy feature: <code>data-sizes=&quot;auto&quot;</code>.</p>
<p>After generating a few different sizes for each image and adding them to the <code>data-srcset</code> attribute, lazysizes can calculate the <code>sizes</code> attribute based on the current display width of the image. The browser can then select the best image from the <code>srcset</code>. Woohoo!</p>
<p>Here's a basic implementation with lazysizes:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fluid-row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fluid-row__item<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--aspect-ratio</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span>
      <span class="token attr-name">data-srcset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image-300.jpg 300w, image-600.jpg 600w, image-900.jpg 900w<span class="token punctuation">"</span></span>
      <span class="token attr-name">data-sizes</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>auto<span class="token punctuation">"</span></span>
      <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lazyload<span class="token punctuation">"</span></span>
      <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image-tiny.jpg<span class="token punctuation">"</span></span>
      <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>My image<span class="token punctuation">"</span></span>
    <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token comment">&lt;!-- more images... --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<h2>Automating with Eleventy and Nunjucks</h2>
<p>Sweet. Now, another issue: there is no way I am going to manually calculate and hardcode the aspect ratio for each image. Let's put this all together and automate it with Eleventy!</p>
<p>We will create a Nunjucks shortcode that:</p>
<ol>
<li>Accepts any number of images and their <code>alt</code> text.</li>
<li>Optionally accepts a caption for the containing <code>figure</code> element.</li>
<li>Uses the Eleventy Image plugin to:
<ul>
<li>Get each image's dimensions,</li>
<li>Calculate the aspect ratio,</li>
<li>Generate multiple sizes of each image, and</li>
<li>Create the proper <code>srcset</code> values.</li>
</ul>
</li>
</ol>
<p>The shortcode looks like this:</p>
<pre class="language-liquid"><code class="language-liquid"><span class="token liquid language-liquid"><span class="token delimiter punctuation">{%</span> imageRow <span class="token punctuation">[</span>
  { src<span class="token operator">:</span> <span class="token string">"one.jpg"</span><span class="token punctuation">,</span> alt<span class="token operator">:</span> <span class="token string">"Alt text"</span> }<span class="token punctuation">,</span>
  { src<span class="token operator">:</span> <span class="token string">"two.jpg"</span><span class="token punctuation">,</span> alt<span class="token operator">:</span> <span class="token string">"Alt text"</span> }<span class="token punctuation">,</span>
  { src<span class="token operator">:</span> <span class="token string">"three.jpg"</span><span class="token punctuation">,</span> alt<span class="token operator">:</span> <span class="token string">"Alt text"</span> }
<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">"Optional caption."</span> <span class="token delimiter punctuation">%}</span></span></code></pre>
<h2>Building the shortcode</h2>
<p>Here’s the full code (I put it in <code>src/shortcodes/imageRow.js</code> and import that into <code>.eleventy.js</code>).</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> Image <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@11ty/eleventy-img'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">imageRow</span><span class="token punctuation">(</span>images<span class="token punctuation">,</span> caption <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> srcDir <span class="token operator">=</span> <span class="token string">'src/images/'</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> outputDir <span class="token operator">=</span> <span class="token string">'dist/images/'</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> imgUrlPath <span class="token operator">=</span> <span class="token string">'/images/'</span><span class="token punctuation">;</span>

  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> imageData <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>
      images<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">image</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> fullImagePath <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>srcDir<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>image<span class="token punctuation">.</span>src<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>

        <span class="token keyword">const</span> metadata <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">Image</span><span class="token punctuation">(</span>fullImagePath<span class="token punctuation">,</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">widths</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">300</span><span class="token punctuation">,</span> <span class="token number">600</span><span class="token punctuation">,</span> <span class="token number">900</span><span class="token punctuation">,</span> <span class="token number">1200</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
          <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'jpeg'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
          <span class="token literal-property property">outputDir</span><span class="token operator">:</span> outputDir<span class="token punctuation">,</span>
          <span class="token literal-property property">urlPath</span><span class="token operator">:</span> imgUrlPath<span class="token punctuation">,</span>
          <span class="token function-variable function">filenameFormat</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">id<span class="token punctuation">,</span> src<span class="token punctuation">,</span> width<span class="token punctuation">,</span> format</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
            <span class="token keyword">const</span> filename <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>src<span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">extname</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filename<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">w.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>format<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">const</span> data <span class="token operator">=</span> metadata<span class="token punctuation">.</span>jpeg<span class="token punctuation">;</span>
        <span class="token keyword">const</span> largestImage <span class="token operator">=</span> data<span class="token punctuation">[</span>data<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">srcset</span><span class="token operator">:</span> data
            <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">entry</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>entry<span class="token punctuation">.</span>url<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>entry<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">w</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">', '</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          <span class="token literal-property property">placeholder</span><span class="token operator">:</span> data<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>url<span class="token punctuation">,</span>
          <span class="token literal-property property">aspectRatio</span><span class="token operator">:</span> largestImage<span class="token punctuation">.</span>width <span class="token operator">/</span> largestImage<span class="token punctuation">.</span>height<span class="token punctuation">,</span>
          <span class="token literal-property property">alt</span><span class="token operator">:</span> image<span class="token punctuation">.</span>alt <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> captionHtml <span class="token operator">=</span> caption
      <span class="token operator">?</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;figcaption class="text-small"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>caption<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/figcaption></span><span class="token template-punctuation string">`</span></span>
      <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;figure>&lt;div class="image-row">
      </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>imageData
        <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>
          <span class="token punctuation">(</span><span class="token parameter">img</span><span class="token punctuation">)</span> <span class="token operator">=></span>
            <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class="image-row__item" style="--aspect-ratio: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>aspectRatio<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">">
              &lt;img src="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>placeholder<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
                   data-srcset="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>srcset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
                   data-sizes="auto"
                   decoding="async"
                   class="lazyload"
                   loading="lazy"
                   alt="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>alt<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">">
            &lt;/div></span><span class="token template-punctuation string">`</span></span>
        <span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
    &lt;/div>
    </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>captionHtml<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
  &lt;/figure></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Error processing image row: '</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class="error">Image could not be displayed.&lt;/div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Let's walk through this bit by bit.</p>
<h3>Include plugins</h3>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> Image <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@11ty/eleventy-img'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Include the Eleventy Image plugin and <code>path</code> (this is available by default through Node; it provides utilities for working with file and directory paths).</p>
<h3>Define the shortcode and path variables</h3>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">imageRow</span><span class="token punctuation">(</span>images<span class="token punctuation">,</span> caption <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> srcDir <span class="token operator">=</span> <span class="token string">'src/images/'</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> outputDir <span class="token operator">=</span> <span class="token string">'dist/images/'</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> imgUrlPath <span class="token operator">=</span> <span class="token string">'/images/'</span><span class="token punctuation">;</span></code></pre>
<p>The parameters accept an array of image objects (each of these will contain a <code>src</code> and <code>alt</code> property), and an optional string to use for the <code>figcaption</code> on the entire row.</p>
<p>I'm also declaring variables to define where my source images are stored, where processed images will be saved, and the URL path to use in the generated HTML. You'll want to adjust these to match your project's directory structure.</p>
<h3>Process each image asynchronously</h3>
<pre class="language-js"><code class="language-js"><span class="token keyword">try</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> imageData <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>
    images<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">image</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> fullImagePath <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>srcDir<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>image<span class="token punctuation">.</span>src<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<p>For each image, we construct the full path to the source file.</p>
<h3>Use Eleventy Image plugin to process the images</h3>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> metadata <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">Image</span><span class="token punctuation">(</span>fullImagePath<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">widths</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">300</span><span class="token punctuation">,</span> <span class="token number">600</span><span class="token punctuation">,</span> <span class="token number">900</span><span class="token punctuation">,</span> <span class="token number">1200</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'jpeg'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token literal-property property">outputDir</span><span class="token operator">:</span> outputDir<span class="token punctuation">,</span>
  <span class="token literal-property property">urlPath</span><span class="token operator">:</span> imgUrlPath<span class="token punctuation">,</span>
  <span class="token function-variable function">filenameFormat</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">id<span class="token punctuation">,</span> src<span class="token punctuation">,</span> width<span class="token punctuation">,</span> format</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> filename <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>src<span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">extname</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filename<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">w.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>format<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Here, we choose the different pixel widths and formats to generate for each image, set the paths, and rename them in a human-readable filename format.</p>
<h3>Collect image metadata</h3>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> data <span class="token operator">=</span> metadata<span class="token punctuation">.</span>jpeg<span class="token punctuation">;</span>
<span class="token keyword">const</span> largestImage <span class="token operator">=</span> data<span class="token punctuation">[</span>data<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">srcset</span><span class="token operator">:</span> data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">entry</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>entry<span class="token punctuation">.</span>url<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>entry<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">w</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">', '</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token literal-property property">placeholder</span><span class="token operator">:</span> data<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>url<span class="token punctuation">,</span>
  <span class="token literal-property property">aspectRatio</span><span class="token operator">:</span> largestImage<span class="token punctuation">.</span>width <span class="token operator">/</span> largestImage<span class="token punctuation">.</span>height<span class="token punctuation">,</span>
  <span class="token literal-property property">alt</span><span class="token operator">:</span> image<span class="token punctuation">.</span>alt <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This constructs the <code>srcset</code>, uses the dimensions of the largest source image to calculate the aspect ratio, and sets the smallest image as our placeholder.</p>
<h3>Build the HTML output</h3>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> captionHtml <span class="token operator">=</span> caption
  <span class="token operator">?</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;figcaption class="text-small"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>caption<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/figcaption></span><span class="token template-punctuation string">`</span></span>
  <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">;</span>

<span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;figure>&lt;div class="image-row">
    </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>imageData
      <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>
        <span class="token punctuation">(</span><span class="token parameter">img</span><span class="token punctuation">)</span> <span class="token operator">=></span>
          <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class="image-row__item" style="--aspect-ratio: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>aspectRatio<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">">
            &lt;img src="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>placeholder<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
                  data-srcset="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>srcset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
                  data-sizes="auto"
                  decoding="async"
                  class="lazyload"
                  loading="lazy"
                  alt="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>img<span class="token punctuation">.</span>alt<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">">
          &lt;/div></span><span class="token template-punctuation string">`</span></span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
  &lt;/div>
  </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>captionHtml<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
&lt;/figure></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<p>Construct the HTML, including the attributes for lazysizes.</p>
<h3>Handle errors</h3>
<pre class="language-js"><code class="language-js"><span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Error processing image row: "</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class="error">Image could not be displayed.&lt;/div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Return an error in the console and HTML if we run into any issues.</p>
<h2>Wrapping up</h2>
<p><strong>And that's it!</strong> You now have a flexible, responsive image row component that automatically calculates aspect ratios and generates optimized images. The shortcode handles all the heavy lifting - just pass in your images and an optional caption. Good times.</p>
<p>You can <a href="https://github.com/minttoothpick/jeremyrobertjones.com/blob/main/src/shortcodes/imageRow.js">view the code on GitHub</a> if you want to adapt it for your own project!</p>

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