Create photo_exif liquid component + update photography blogs to use it

This commit is contained in:
2026-03-23 23:56:22 -05:00
parent c25e2ac380
commit 24b45a4945
19 changed files with 241 additions and 50 deletions

View File

@@ -4,6 +4,9 @@ import brokenLinksPlugin from 'eleventy-plugin-broken-links'
import { eleventyImageTransformPlugin } from '@11ty/eleventy-img' import { eleventyImageTransformPlugin } from '@11ty/eleventy-img'
import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight' import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight'
import footnote from 'markdown-it-footnote' import footnote from 'markdown-it-footnote'
import fetch from 'node-fetch'
import fs from 'node:fs'
import exifParser from 'exif-parser'
import { EleventyRenderPlugin, IdAttributePlugin } from '@11ty/eleventy' import { EleventyRenderPlugin, IdAttributePlugin } from '@11ty/eleventy'
import { setupBlogCollections } from './scripts/eleventy/blog.js' import { setupBlogCollections } from './scripts/eleventy/blog.js'
import { setupFeedCollections } from './scripts/eleventy/feed.js' import { setupFeedCollections } from './scripts/eleventy/feed.js'
@@ -33,6 +36,16 @@ const setupPlugins = eleventyConfig => {
eleventyConfig.addLiquidFilter('dateToRfc822', rssPlugin.dateToRfc822) eleventyConfig.addLiquidFilter('dateToRfc822', rssPlugin.dateToRfc822)
eleventyConfig.addLiquidFilter('dateToRfc3339', rssPlugin.dateToRfc3339) eleventyConfig.addLiquidFilter('dateToRfc3339', rssPlugin.dateToRfc3339)
eleventyConfig.addLiquidFilter('getNewestCollectionItemDate', rssPlugin.getNewestCollectionItemDate) eleventyConfig.addLiquidFilter('getNewestCollectionItemDate', rssPlugin.getNewestCollectionItemDate)
eleventyConfig.addLiquidFilter('parseExif', async (src) => {
const content = src.startsWith('https://')
? await fetch(src).then(r => r.arrayBuffer())
: fs.readFileSync(src)
const result = exifParser.create(content).parse()
console.log({ result })
return result
})
} }
export default function (eleventyConfig) { export default function (eleventyConfig) {

View File

@@ -25,6 +25,7 @@
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"canvas": "^3.1.0", "canvas": "^3.1.0",
"eleventy-plugin-broken-links": "^2.2.1", "eleventy-plugin-broken-links": "^2.2.1",
"exif-parser": "^0.1.12",
"favicons": "^7.2.0", "favicons": "^7.2.0",
"markdown-it-footnote": "^4.0.0", "markdown-it-footnote": "^4.0.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",

8
pnpm-lock.yaml generated
View File

@@ -29,6 +29,9 @@ importers:
eleventy-plugin-broken-links: eleventy-plugin-broken-links:
specifier: ^2.2.1 specifier: ^2.2.1
version: 2.2.1 version: 2.2.1
exif-parser:
specifier: ^0.1.12
version: 0.1.12
favicons: favicons:
specifier: ^7.2.0 specifier: ^7.2.0
version: 7.2.0 version: 7.2.0
@@ -643,6 +646,9 @@ packages:
eventemitter3@4.0.7: eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
exif-parser@0.1.12:
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
expand-template@2.0.3: expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -2176,6 +2182,8 @@ snapshots:
eventemitter3@4.0.7: {} eventemitter3@4.0.7: {}
exif-parser@0.1.12: {}
expand-template@2.0.3: {} expand-template@2.0.3: {}
extend-shallow@2.0.1: extend-shallow@2.0.1:

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-aperture-icon lucide-aperture"><circle cx="12" cy="12" r="10"/><path d="m14.31 8 5.74 9.94"/><path d="M9.69 8h11.48"/><path d="m7.38 12 5.74-9.94"/><path d="M9.69 16 3.95 6.06"/><path d="M14.31 16H2.83"/><path d="m16.62 12-5.74 9.94"/></svg>

After

Width:  |  Height:  |  Size: 462 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-icon lucide-arrow-up"><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></svg>

After

Width:  |  Height:  |  Size: 303 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days-icon lucide-calendar-days"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-camera-icon lucide-camera"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/></svg>

After

Width:  |  Height:  |  Size: 475 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-crop-icon lucide-crop"><path d="M6 2v14a2 2 0 0 0 2 2h14"/><path d="M18 22V8a2 2 0 0 0-2-2H2"/></svg>

After

Width:  |  Height:  |  Size: 322 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-locate-fixed-icon lucide-locate-fixed"><line x1="2" x2="5" y1="12" y2="12"/><line x1="19" x2="22" y1="12" y2="12"/><line x1="12" x2="12" y1="2" y2="5"/><line x1="12" x2="12" y1="19" y2="22"/><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="3"/></svg>

After

Width:  |  Height:  |  Size: 461 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-map-pinned-icon lucide-map-pinned"><path d="M18 8c0 3.613-3.869 7.429-5.393 8.795a1 1 0 0 1-1.214 0C9.87 15.429 6 11.613 6 8a6 6 0 0 1 12 0"/><circle cx="12" cy="8" r="2"/><path d="M8.714 14h-3.71a1 1 0 0 0-.948.683l-2.004 6A1 1 0 0 0 3 22h18a1 1 0 0 0 .948-1.316l-2-6a1 1 0 0 0-.949-.684h-3.712"/></svg>

After

Width:  |  Height:  |  Size: 525 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rabbit-icon lucide-rabbit"><path d="M13 16a3 3 0 0 1 2.24 5"/><path d="M18 12h.01"/><path d="M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3"/><path d="M20 8.54V4a2 2 0 1 0-4 0v3"/><path d="M7.612 12.524a3 3 0 1 0-1.6 4.3"/></svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@@ -0,0 +1,47 @@
{% assign exif = src | parseExif %}
<div class="photo-exif">
<div class="img-wrapper">
<img src="{{ src }}" alt="Image: {{ title }}">
</div>
<div class="exif-wrapper">
<ul>
<li class="title">{{ title }}</li>
<li>
{%- include './icons/calendar' -%}
<div class="value">{{ exif.tags.CreateDate | date:'%b %Y' }}</div>
</li>
{%- if location -%}
<li>
{%- include './icons/map-pin' %}
<div class="value">{{ location }}</div>
</li>
{%- endif -%}
{%- if exif.tags.GPSLatitude %}
<li>
{%- include './icons/locate-fixed' -%}
<div class="value">{{ exif.tags.GPSLatitude | round:3 }}, {{ exif.tags.GPSLongitude | round:3 }}</div>
</li>
{% endif -%}
<li>
{%- include './icons/camera' -%}
<div class="value">{{ exif.tags.Model }}</div>
</li>
<li>
{%- include './icons/arrow-up' -%}
<div class="value">ISO {{ exif.tags.ISO }}</div>
</li>
<li>
{%- include './icons/aperture' -%}
<div class="value">f/{{ exif.tags.FNumber }}</div>
</li>
<li>
{%- include './icons/rabbit' -%}
<div class="value">{{ exif.tags.ExposureTime }}</div>
</li>
<li>
{%- include './icons/crop' -%}
<div class="value">{{ exif.tags.ExifImageWidth }} ✕ {{ exif.tags.ExifImageHeight }}</div>
</li>
</ul>
</div>
</div>

View File

@@ -1,12 +1,11 @@
body { body {
--background: #111; --background: #111;
--background-2: #252525; --background-2: #252525;
--background-3: #444; --background-3: #444;
--color: #fffbe3; --color: #fffbe3;
--color-2: #d0c895; --color-2: #d0c895;
--color-3: #999;
--content-width: 800px;
background: var(--background); background: var(--background);
color: var(--color); color: var(--color);
@@ -20,7 +19,7 @@ body {
} }
.wrapper { .wrapper {
max-width: 800px; max-width: var(--content-width);
width: calc(100% - 40px); width: calc(100% - 40px);
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
@@ -288,6 +287,74 @@ td, th {
white-space: nowrap; white-space: nowrap;
} }
.photo-exif {
display: flex;
flex-direction: row;
gap: 10px;
margin-top: 30px;
margin-bottom: 30px;
width: calc(var(--content-width) + 260px);
}
.photo-exif .img-wrapper img {
margin: 0;
border-radius: 15px;
max-height: calc(100vh - 20px);
width: auto;
}
.photo-exif .exif-wrapper {
min-width: 250px;
}
.photo-exif .exif-wrapper ul {
list-style-type: none;
margin: 0;
color: var(--color-3);
background: var(--background-2);
padding: 10px;
border-radius: 15px;
}
.photo-exif .exif-wrapper ul li {
display: flex;
flex-direction: row;
margin: 7px 0;
}
.photo-exif .exif-wrapper ul li svg {
min-width: 24px;
max-width: 24px;
min-height: 24px;
max-height: 24px;
margin-right: 7px;
}
.photo-exif .exif-wrapper ul .title {
color: var(--color);
font-style: italic;
}
@media screen and (max-width: 1270px) {
/* 1020px = 800px (max content width) + 470px (.photo-exif title card width * 2) */
/* Below that, reformat for portrait mode. */
.photo-exif {
flex-direction: column;
width: unset;
}
.photo-exif .exif-wrapper ul {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 0 20px;
}
.photo-exif .exif-wrapper ul .title {
flex-basis: 100%;
}
}
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
nav ul { nav ul {
flex-direction: column; flex-direction: column;

View File

@@ -7,7 +7,8 @@
<div class="recent-posts"> <div class="recent-posts">
<ul class="plain"> <ul class="plain">
{% for post in collections.blog reversed limit:10 %} {% assign posts = collections.blog | reverse %}
{% for post in posts limit:10 %}
<li> <li>
<div class="secondary">{{ post.data.date | date: "%Y-%m-%d" }}</div> <div class="secondary">{{ post.data.date | date: "%Y-%m-%d" }}</div>
<a class="title" href="{{ post.url }}">{{ post.data.title }}</a> <a class="title" href="{{ post.url }}">{{ post.data.title }}</a>

View File

@@ -18,7 +18,11 @@ The first challenge is to capture a self-portrait. This one was interesting beca
I was a bit stumped on what to do for this, until I was sitting at my desk and noticed the annoying glare my reflection made in my monitor thanks to the light from the window. So, I decided to run with that, and I'm fairly pleased with the result: I was a bit stumped on what to do for this, until I was sitting at my desk and noticed the annoying glare my reflection made in my monitor thanks to the light from the window. So, I decided to run with that, and I'm fairly pleased with the result:
![](https://static.garrettmills.dev/assets/blog-images/photo-challenge/01-self-portrait.jpg) {%
include '../../_includes/photo_exif',
src:'https://static.garrettmills.dev/assets/blog-images/photo-challenge/01-self-portrait.jpg',
title:'Self-Portrait.',
%}
I had to play a bit with the color balance in editing. Getting my reflection to be defined enough to stand out strikingly on the monitor was a challenge. To help, I added a couple artificial lights off the right side of the frame to supplement the light coming from the window. I had to play a bit with the color balance in editing. Getting my reflection to be defined enough to stand out strikingly on the monitor was a challenge. To help, I added a couple artificial lights off the right side of the frame to supplement the light coming from the window.

View File

@@ -11,13 +11,18 @@ blogtags:
I'm not particularly skilled or versatile, but I enjoy dabbling in amateur photography occasionally. Last weekend was Commencement at the University of Kansas. My partner and some of our friends graduated, so I got to play paparazzi for the weekend. Here are a couple miscellaneous photos that I particularly enjoyed: I'm not particularly skilled or versatile, but I enjoy dabbling in amateur photography occasionally. Last weekend was Commencement at the University of Kansas. My partner and some of our friends graduated, so I got to play paparazzi for the weekend. Here are a couple miscellaneous photos that I particularly enjoyed:
<img src="https://static.garrettmills.dev/assets/blog-images/IMG_2655.jpg"> {%
include '../../_includes/photo_exif',
src:'https://static.garrettmills.dev/assets/blog-images/IMG_2655.jpg',
title:'Spring Tulips.',
location: '<a href="https://places.ku.edu/fountain/chi-omega-fountain" target="_blank">Chi Omega Fountain</a>, Lawrence, KS'
%}
<center><small>A photo of some spring tulips, taken outside the <a href="https://places.ku.edu/fountain/chi-omega-fountain" target="_blank">Chi Omega fountain</a> on campus.</small></center> {%
include '../../_includes/photo_exif',
src:'https://static.garrettmills.dev/assets/blog-images/IMG_3365.jpg',
title:'Untitled.',
<img src="https://static.garrettmills.dev/assets/blog-images/IMG_3365.jpg"> location: 'Lawrence, KS'
%}
<center><small>My personal favorite from the weekend: a photo of one of the performance planes, taken outside the stadium as the commencement ceremony was starting.</small></center>
My personal favorite from the weekend: a photo of one of the performance planes, taken outside the stadium as the commencement ceremony was starting.

View File

@@ -13,22 +13,37 @@ blogtags:
Here are some miscellaneous photos I've taken since the [last photo-dump](https://garrettmills.dev/blog/2024/05/15/Miscellaneous-Photos-from-Commencement-2024/). Here are some miscellaneous photos I've taken since the [last photo-dump](https://garrettmills.dev/blog/2024/05/15/Miscellaneous-Photos-from-Commencement-2024/).
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202504/p1.jpg"> include '../../_includes/photo_exif',
<center><small><em>One Leg.</em> Port of New Orleans, New Orleans, Louisiana. (December 2024)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202504/p1.jpg',
title:'One Leg.',
location: 'Port of New Orleans, New Orleans, LA'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202504/p2.jpg"> include '../../_includes/photo_exif',
<center><small><em>A. Thomas Higgins.</em> Port of New Orleans, New Orleans, Louisiana. (December 2024)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202504/p2.jpg',
title:'A. Thomas Higgins.',
location: 'Port of New Orleans, New Orleans, LA'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202504/p3.jpg"> include '../../_includes/photo_exif',
<center><small><em>Snowstorm.</em> Downtown, Indianapolis, Indiana. (January 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202504/p3.jpg',
title:'Snowstorm.',
location: 'Downtown Indianapolis, IN'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202504/p4.jpg"> include '../../_includes/photo_exif',
<center><small><em>Untitled.</em> Fort Collins, Colorado. (March 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202504/p4.jpg',
title:'Untitled.',
location: 'Fort Collins, CO'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202504/p5.jpg"> include '../../_includes/photo_exif',
<center><small><em>State Line.</em> Downtown, Indianapolis, Indiana. (April 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202504/p5.jpg',
title:'State Line.',
location: 'Downtown Indianapolis, IN'
%}

View File

@@ -11,30 +11,51 @@ blogtags:
Welcome to my periodic "I swear this isn't a photography blog, I just think they're neat" post. You may also like the [last photo-dump](/blog/2025/04/16/Miscellaneous-Photos-April-2025/). Welcome to my periodic "I swear this isn't a photography blog, I just think they're neat" post. You may also like the [last photo-dump](/blog/2025/04/16/Miscellaneous-Photos-April-2025/).
<br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/indy-parade.jpg"> include '../../_includes/photo_exif',
<center><small><em>No Hands.</em> Indy 500 Parade, Indianapolis, Indiana. (May 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/indy-parade.jpg',
title:'No Hands.',
location: 'Indy 500 Parade, Indianapolis, IN'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/hotrod.jpg"> include '../../_includes/photo_exif',
<center><small><em>Hot Rod.</em> Colorado Springs, Colorado. (September 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/hotrod.jpg',
title:'Hot Rod.',
location: 'Colorado Springs, CO'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/human-scale.jpg"> include '../../_includes/photo_exif',
<center><small><em>Human-Scale.</em> Colorado Springs, Colorado. (September 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/human-scale.jpg',
title:'Human-Scale.',
location: 'Colorado Springs, CO'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/texture.jpg"> include '../../_includes/photo_exif',
<center><small><em>Texture.</em> Colorado Springs, Colorado. (September 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/texture.jpg',
title:'Texture.',
location: 'Colorado Springs, CO'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/15-minute.jpg"> include '../../_includes/photo_exif',
<center><small><em>15-Minute City.</em> Mission, Kansas. (August 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/15-minute.jpg',
title:'15-Minute City.',
location: 'Mission, KS'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/mystery.jpg"> include '../../_includes/photo_exif',
<center><small><em>Mystery.</em> Lawrence, Kansas. (September 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/mystery.jpg',
title:'Untitled.',
location: 'Lawrence, KS'
%}
<br><br> {%
<img src="https://static.garrettmills.dev/assets/blog-images/photos-202603/side-eyes.jpg"> include '../../_includes/photo_exif',
<center><small>👀. Colorado Springs, Colorado. (September 2025)</small></center> src:'https://static.garrettmills.dev/assets/blog-images/photos-202603/side-eyes.jpg',
title:'👀',
location: 'Colorado Springs, CO'
%}

View File

@@ -14,6 +14,7 @@ The current vibe feels like less. Less visual complexity, less scope, less techn
This design is my attempt to return to a [simpler](https://motherfuckingwebsite.com/) [design](http://bettermotherfuckingwebsite.com/) [language](https://thebestmotherfucking.website/) that better conveys my current mood, with really (_really_) good fonts on a solid background. This design is my attempt to return to a [simpler](https://motherfuckingwebsite.com/) [design](http://bettermotherfuckingwebsite.com/) [language](https://thebestmotherfucking.website/) that better conveys my current mood, with really (_really_) good fonts on a solid background.
Some icons (particularly those in the photo metadata sidebar) are used from [Lucide](https://lucide.dev/) under the terms of the ISC License.
### Fonts ### Fonts