From 58de9047735bf24924384b62964dabfce7239c71 Mon Sep 17 00:00:00 2001 From: Shannon Atkinson Date: Thu, 13 Aug 2026 20:38:26 -0700 Subject: [PATCH 1/2] feat(web): a lightbox for the feature screenshots, and the third font preloaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TWO ITEMS FROM THE SITE REVIEW, plus a request. THE LIGHTBOX. Every screenshot on /features is 2880x1800 rendered into roughly half a column, so the UI detail the page is arguing about — a track matrix, a loudness target — is unreadable at the size it is shown. Clicking now expands it to fit the viewport, and Escape, the close button or the backdrop puts it back. Built on rather than a div, because the three things a hand-rolled overlay reliably gets wrong come free: Escape closes it, focus is trapped while open, and focus returns to the button that opened it. ::backdrop is a real element so the dimming needs no extra node. One shared dialog for all six shots rather than six dialogs. The buttons carry the source and the caption; the dialog fills itself in. Its src is dropped on close so a 2880x1800 decode is not held for something nobody is looking at. Progressive: with JavaScript off the images render exactly as before. The button is an affordance, and nothing depends on it. The trigger wraps the whole image rather than sitting beside it — a small magnifier icon would be a worse target than the 2880-wide picture it sits on. Verified in a real browser, not by reading the markup: opens with the right image and caption, focus lands on the close button, Escape closes AND returns focus to the trigger it came from, the close button closes, a backdrop click closes, and a click inside the box does not. margin: auto is stated explicitly, and that is not decoration. Tailwind's preflight resets the UA stylesheet's `dialog { margin: auto }` to 0, so a modal dialog pins to the top left: measured at left=0 with a 167px gap on the right in a 1200px viewport. With the line, 76px each side of the layout viewport. THE FONT. jetbrains-mono was the one face not preloaded, and it is above the fold on every page — the hero eyebrow, the `docker run` line a visitor copies, the footer column labels. Measured on the live site over Slow 4G it began loading 592ms after its two preloaded siblings, because the browser only learned it was needed once the stylesheet had been parsed. No extra bytes; the file is fetched either way. --- web/src/components/Lightbox.astro | 164 ++++++++++++++++++++++++++++++ web/src/layouts/Base.astro | 9 ++ web/src/pages/features.astro | 35 +++++-- 3 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 web/src/components/Lightbox.astro diff --git a/web/src/components/Lightbox.astro b/web/src/components/Lightbox.astro new file mode 100644 index 00000000..dfd76e9b --- /dev/null +++ b/web/src/components/Lightbox.astro @@ -0,0 +1,164 @@ +--- +/* One dialog for every screenshot on the page, rather than one per image. + * + * WHY A SHARED DIALOG. The features page carries six 2880x1800 screenshots. Six + * dialogs would mean six copies of the markup and six more elements the + * browser has to consider, for a UI that can only ever show one at a time. The + * buttons carry the source and the caption; this fills itself in on open. + * + * WHY AND NOT A DIV. The three things a hand-rolled overlay reliably + * gets wrong are all free here: Escape closes it, focus is trapped inside while + * it is open, and focus returns to the button that opened it when it closes. + * ::backdrop is a real element, so the dimming needs no extra node. + * + * PROGRESSIVE. Without JavaScript the inside each button still renders at + * its normal size and the page reads exactly as it did before. The button is + * only an affordance; nothing depends on it. + */ +--- + + + + {/* alt is set from the trigger, so the expanded view says the same thing the + inline one did rather than going unlabelled. */} + + + + + + + diff --git a/web/src/layouts/Base.astro b/web/src/layouts/Base.astro index b9d2a02e..375f4c65 100644 --- a/web/src/layouts/Base.astro +++ b/web/src/layouts/Base.astro @@ -72,6 +72,15 @@ const ogImage = new URL("/og.png", Astro.site ?? "https://polyemesis.com"); system is 94 KB — one file per family covering every weight used. */} + {/* THE THIRD FACE WAS THE ONE LEFT OUT, and it is above the fold on every + page: the hero eyebrow, the `docker run` line a visitor copies, the + footer column labels. Measured on the live site over Slow 4G it began + loading 592ms after its two preloaded siblings, because the browser only + learned it was needed once the stylesheet had been parsed. + + No extra bytes — the file is fetched either way. This only moves the + discovery earlier. */} +