site: rename "finds" superposts → "bundles" + 2026-05-06 capture run
Collection rename: src/content/finds/ → src/content/bundles/, URLs /finds/<slug>/ → /bundles/<slug>/, internal symbols (FindsPost → Bundle, findListMosaic → bundleMosaic, findsReferencing → bundlesReferencing, Entry.type 'finds' → 'bundle'). Skills build-finds/cluster-finds renamed to build-bundle/cluster-bundles. The `find` collection (individual items) is unchanged. New content: 4 reviews graduated (finalist, gum-shell, tablepro, valchoose-barf-bags) and 6 bundles for 2026-05-06 (constrain-the-surface, menu-bar-singletons, on-device-by-design, six-american-detours, six-lines-six-origins, things-in-the-terminal). Adds EntryGrid component.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "Constrain the surface"
|
||||
date: 2026-05-06
|
||||
tags: [daily-finds, productivity, focus, macos-apps]
|
||||
description: "Five strategies for cutting the day's input down to a single column, a single task, or a single empty page."
|
||||
blurb: "Five strategies for the same problem — too many surfaces, all open at once."
|
||||
items:
|
||||
- one-thing-menu-bar
|
||||
- finalist
|
||||
- hour-by-hour
|
||||
- finder-tags-one-to-one
|
||||
- start-with-nothing
|
||||
---
|
||||
|
||||
> Today's catch is five strategies for the same problem — too many surfaces, all open at once. Four apps and an essay, each one cutting the day's input down to whatever fits inside a single line, a single column, or a single empty page.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "Menu-bar singletons"
|
||||
date: 2026-05-06
|
||||
tags: [daily-finds, macos-apps, menu-bar]
|
||||
description: "Six menu-bar singletons, each one doing exactly one thing inside the same square inch of screen."
|
||||
blurb: "Six small utilities that share a square inch of screen and each do exactly one thing."
|
||||
items:
|
||||
- betteraudio
|
||||
- one-thing-menu-bar
|
||||
- pingplace
|
||||
- mac-juice-monitor
|
||||
- mister-plimsoll
|
||||
- extrabar
|
||||
---
|
||||
|
||||
> Today's catch is six small utilities that all live in the same square inch of screen and each do exactly one thing — audio routing, a single visible task, where notifications appear, Bluetooth battery levels, disk pressure, and one rethink of the menu bar itself.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "On-device, by design"
|
||||
date: 2026-05-06
|
||||
tags: [daily-finds, macos-apps, privacy, local-first]
|
||||
description: "Four Mac apps that keep your data on your machine — redaction, on-device LLM, live transcription, and a local-first personal library."
|
||||
blurb: "Four Mac apps with the same thesis — the private thing stays on the private machine."
|
||||
items:
|
||||
- hidemydata
|
||||
- sentient-os
|
||||
- transcribex
|
||||
- resurf
|
||||
---
|
||||
|
||||
> Today's catch is four Mac apps with the same thesis — that the private thing should stay on the private machine. Redaction, transcription, indexing, and a personal library, all on-device, none of them making a cloud round-trip on your behalf.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "Six American detours"
|
||||
date: 2026-05-06
|
||||
tags: [daily-finds, travel, americana]
|
||||
description: "Six small American places worth the steer — three pieces of roadside kitsch, three corners of public land, none on the canonical tourist itinerary."
|
||||
blurb: "Roadside kitsch, public land, and one 45-ton concrete vault — six American detours, none over $15."
|
||||
items:
|
||||
- gemini-giant-route-66
|
||||
- seward-time-capsule
|
||||
- weeki-wachee-springs
|
||||
- fall-creek-falls-tennessee
|
||||
- kirby-cove
|
||||
- wildwood-trail-portland
|
||||
---
|
||||
|
||||
> Today's catch is American: three pieces of roadside kitsch that outlasted the highways that built them, and three corners of public land where the trail or the water does the work. None on the canonical itinerary, none over $15.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "Six lines, six origins"
|
||||
date: 2026-05-06
|
||||
tags: [daily-finds, quotes, books, philosophy]
|
||||
description: "Six literary fragments captured in a single morning — quotes, a poem, and two attribution investigations, each paired with the work it came from."
|
||||
blurb: "Three book quotes, one poem, and two attribution investigations — six fragments, six honest sources."
|
||||
items:
|
||||
- arendt-pariah-humanity
|
||||
- heller-the-enemy-quote
|
||||
- machiavelli-force-fraud
|
||||
- mary-oliver-fourth-sign
|
||||
- stendhal-gods-excuse
|
||||
- wayne-dyer-change-look
|
||||
---
|
||||
|
||||
> Today's catch is six fragments from one morning of reading — three from books, one a poem, and two old quote investigations that nailed a long-running misattribution. Each entry ships with the work it actually came from.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "Things in the terminal"
|
||||
date: 2026-05-06
|
||||
tags: [daily-finds, terminal, developer-tools, cli]
|
||||
description: "Four ways to make the terminal earn its window — a shell-UI library, a git heatmap, an RSS reader, and a desktop-widget pipe."
|
||||
blurb: "A shell-UI library, a git heatmap, an RSS reader, and a way to pipe any of it into a desktop widget."
|
||||
items:
|
||||
- gum-shell
|
||||
- heatwave-git
|
||||
- nom-rss
|
||||
- terminal-widget
|
||||
---
|
||||
|
||||
> Today's catch is four small tools that earn the rectangle they live in — a UI library for shell scripts, a visual heatmap of git activity, an RSS reader inside the prompt, and a way to pipe any of it back out into a desktop widget. None of them require a window manager.
|
||||
@@ -6,6 +6,7 @@ link: https://finalist.works
|
||||
source: "Daring Fireball — Linked List"
|
||||
topics: [macos-apps]
|
||||
tags: [ios, macos, planning, productivity]
|
||||
promotedTo: "finalist"
|
||||
---
|
||||
|
||||
Designed around the rules of a paper-based daily planning method: a finite list per day, you carry forward what didn't get done, you do not infinitely add. The constraint is the feature — Finalist won't let you build the impossible to-do list. Available on iPhone, iPad, and Mac.
|
||||
|
||||
@@ -6,6 +6,7 @@ link: https://github.com/charmbracelet/gum
|
||||
source: "One Thing Well"
|
||||
topics: [tools]
|
||||
tags: [shell, terminal, scripting, open-source]
|
||||
promotedTo: "gum-shell"
|
||||
---
|
||||
|
||||
A toolkit from Charm that lets bash and zsh scripts call out to pre-built TUI components — fuzzy choosers, password inputs, spinners, multi-line confirmations — so a fifty-line script can have a feel closer to a polished CLI tool than a wall of text. Drop-in replacement for `read` and `select` when you want script ergonomics without rewriting it in Go. The components compose: pipe `gum choose` into `gum confirm` into `gum spin`.
|
||||
|
||||
@@ -9,6 +9,7 @@ topics:
|
||||
- macos-apps
|
||||
tags: [database, developer-tools, open-source, sql]
|
||||
description: "One Mac-native client across 18 database flavours — relational, key-value, document, analytical, cloud — with an MCP server included."
|
||||
promotedTo: "tablepro"
|
||||
---
|
||||
|
||||
Most Mac database clients force a choice: TablePlus is closed and paid, Sequel Ace only does MySQL, DBeaver is Java pretending to be a Mac app. TablePro is the native, free, open-source middle path — one window for Postgres, MySQL, SQLite, Mongo, Redis, DynamoDB, and a dozen more, with an MCP server bundled so AI agents can drive it. iPad version is in TestFlight.
|
||||
|
||||
@@ -9,6 +9,7 @@ topics:
|
||||
- tools
|
||||
tags: [first-aid, car, kids, travel, kit]
|
||||
description: "1000mL hospital-grade barf bags with a rigid white plastic ring at the mouth — keeps itself open hands-free, twists into the ring's eyelet groove to seal after use."
|
||||
promotedTo: "valchoose-barf-bags"
|
||||
---
|
||||
|
||||
Most "vomit bag" solutions in a household are an empty grocery sack and a prayer. The Valchoose bags are the same thing hospitals hand out — a deep blue 1000mL pouch fixed to a rigid white plastic ring that holds itself open without anyone needing to grip the rim. That detail matters when the user is a kid in a car seat, a passenger in the dark, or someone in the middle of a wave of nausea: the bag stays where you point it. After use, the ring has an eyelet groove so you can twist the bag closed and lock the twist into the ring for a contained, leak-resistant disposal.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: "Finalist"
|
||||
subtitle: "Daily planner app modeled on a paper notebook"
|
||||
date: 2026-05-06
|
||||
category: planning
|
||||
tags: [ios, macos, planning, productivity, focus]
|
||||
link: "https://finalist.works"
|
||||
linkText: "finalist.works"
|
||||
description: "A daily planner that won't let you add to today after the day starts — the constraint is the feature."
|
||||
source: "Daring Fireball — Linked List"
|
||||
fromFind: "finalist"
|
||||
---
|
||||
|
||||

|
||||
|
||||
*Finalist on iPhone — the day's list is exactly the day's list, with arrows marking what carried over from yesterday.*
|
||||
|
||||
> A planner app whose entire pitch is what it refuses to do. After the day starts you can't add to it; whatever didn't get done carries forward to tomorrow with an arrow in front of it, intact.
|
||||
|
||||
Most planning apps treat your list as an infinite append-only log: type a thing, it goes in, the list grows. The implicit rule is that whatever you can think of, you can promise yourself you'll do. Finalist is the inverse. The day's list is fixed at start-of-day, and the only operations after that are *do* and *defer*. There is no "actually, also this." The constraint is the feature.
|
||||
|
||||
The model is borrowed wholesale from a paper-planner discipline — Bullet Journal-adjacent, but tighter. Yesterday's incomplete items show up in today's list with a leading arrow (›) to mark them as carry-forwards, the way a paper user would migrate them by hand. The arrow is small but does serious work: an item carried four days running is visible as such, which forces a decision — do it, schedule it, or admit it's not happening and delete it. The app makes the friction of that admission small enough that you actually do it.
|
||||
|
||||
Available on iPhone, iPad, and Mac with the same view across all three; sync is iCloud-based, no account required. The interface is deliberately plain — a list, a date, an arrow column — so the constraint stays the visible feature rather than getting buried under typography.
|
||||
|
||||
Honest limitation: this only works if the underlying discipline matches your temperament. If you genuinely use today's list as a place to capture every passing thought as it arrives, Finalist will fight you. It's built for the user who wants the app to fight them about that exact habit. For the rest of us, it's a quiet nudge in the right direction every morning.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 305 KiB |
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: "gum"
|
||||
subtitle: "Polished shell-script UI components — choose, input, spinner, confirm"
|
||||
date: 2026-05-06
|
||||
category: terminal
|
||||
tags: [shell, terminal, scripting, open-source, charm, cli]
|
||||
link: "https://github.com/charmbracelet/gum"
|
||||
description: "A Charm toolkit that gives bash and zsh scripts proper TUI components — fuzzy menus, password inputs, spinners — composable with pipes."
|
||||
source: "One Thing Well"
|
||||
fromFind: "gum-shell"
|
||||
---
|
||||
|
||||

|
||||
|
||||
*Gum's `choose` component as a drop-in replacement for `read` and `select` — arrow-key fuzzy menu, no Go required.*
|
||||
|
||||
> A toolkit from Charm that turns bash and zsh scripts into things you'd actually want to share — fuzzy choosers, password prompts, spinners, multi-line confirmations — composable with pipes.
|
||||
|
||||
The shell's built-in interaction primitives stop at `read` and `select`. They've been there since the 1970s and they look it. Anything fancier traditionally meant rewriting your fifty-line script in Go, Python, or Node — and watching it grow into a thousand-line project nobody else on the team can reach into. Gum is the alternative: a small statically-linked binary, called like any other shell command, that gives you a Charm-quality TUI inside whatever script you've already written.
|
||||
|
||||
The components are the whole product: `gum choose` for menus, `gum input` for text fields (with a `--password` flag), `gum spin` for progress, `gum confirm` for yes/no, `gum filter` for fuzzy pickers, `gum write` for multi-line text, `gum format` for styled output. Each writes its result to stdout, so they pipe naturally — `gum choose option1 option2 | xargs -I {} gum confirm "Do {}?"` reads like a sentence rather than a Bash maze. The styling matches Glow, Lazygit, and the rest of the Charm catalog — rounded borders, subtle colors, restrained motion — with config cascading from CLI flags to environment variables to a `.gum.toml`.
|
||||
|
||||
Install via `brew install gum`, `apt-get install gum`, `go install`, or grab the static binary; no runtime dependency. Open source, MIT-licensed.
|
||||
|
||||
Honest limitation: it's a hard dependency in your script. Anyone running your function or installer needs `gum` on `$PATH`, which means either a bootstrap step at the top of the script or a Homebrew/apt declaration. For internal tools that's fine; for a "curl | bash" public installer it's friction worth weighing against the readability win.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: "TablePro"
|
||||
subtitle: "A free, native, open-source database client for macOS"
|
||||
date: 2026-05-06
|
||||
category: indie
|
||||
tags: [macos, database, developer-tools, open-source, sql, mcp]
|
||||
link: "https://tablepro.app/"
|
||||
linkText: "tablepro.app"
|
||||
description: "Eighteen database engines in a single Mac-native window — relational, key-value, document, analytical, cloud — with a built-in MCP server."
|
||||
source: "r/macapps"
|
||||
fromFind: "tablepro"
|
||||
---
|
||||
|
||||

|
||||
|
||||
*TablePro's SQL editor running against the bundled Chinook sample database — connection pane on the left, results below.*
|
||||
|
||||
> The Mac-native database client that finally consolidates the eighteen engines a typical developer touches into a single window — and ships an MCP server so Claude or Copilot can drive it.
|
||||
|
||||
The macOS database-client landscape forces a compromise. TablePlus is polished but closed and paid. Sequel Ace is free and native but only speaks MySQL. DBeaver covers everything but is Java pretending to be a Mac app — startup latency, off-brand widgets, the works. None of them are open source in the way you can fork and fix.
|
||||
|
||||
TablePro is the missing native, free, open-source middle path. One window for Postgres, MySQL, SQLite, Redis, Mongo, ClickHouse, DuckDB, MSSQL, Cassandra, etcd, Cloudflare D1, DynamoDB, BigQuery, LibSQL, Oracle, and Redshift. The first six are bundled; the rest install on demand so the app stays small until you reach for them. Apple Silicon and Intel, macOS 14+, starts in under a second.
|
||||
|
||||
The MCP server is the angle that earns it a place on the desk now rather than next year. Point Claude Desktop, Cursor, or any MCP-aware client at TablePro and the same connection pool you've been using becomes a tool the model can drive — describe the schema, run the explain plan, sample a thousand rows, draft the migration. That development loop doesn't exist with TablePlus or DBeaver. Inline AI suggestions and a chat pane wired to a Copilot subscription or any OpenAI-compatible key cover the case where you'd rather query the model directly.
|
||||
|
||||
Beyond the engines and the AI, the basics are honestly handled: SSH tunnels, ER diagrams, query history, undo/redo on cell edit, iCloud sync for connections and tabs across your Macs. A bundled Chinook sample database means you can poke at the UI without wiring anything up. The iPad and iPhone builds are in public TestFlight; a Linux build is in progress.
|
||||
|
||||
Honest limitations: five months old, the maintainer's first big indie release, and the rough-edges list in the launch post is candid — features still landing, some engines more polished than others. AGPLv3 means if you fork it and ship a hosted service you owe your changes back; for a desktop user that's invisible, for anyone planning to wrap it as SaaS that's the whole catch.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: "Valchoose Disposable Barf Bags"
|
||||
subtitle: "Hospital-grade 1000mL vomit bags with a rigid plastic ring that holds itself open"
|
||||
date: 2026-05-06
|
||||
category: kit
|
||||
tags: [first-aid, car, kids, travel, kit, household]
|
||||
link: "https://www.amazon.com/dp/B083J2S3Q6/"
|
||||
linkText: "View on Amazon"
|
||||
description: "Hospital-grade 1000mL vomit bags with a rigid plastic ring that holds itself open hands-free — the kind of object that's invisible until it's the only thing you want."
|
||||
source: "Reader suggestion"
|
||||
fromFind: "valchoose-barf-bags"
|
||||
---
|
||||
|
||||

|
||||
|
||||
*The Valchoose 1000mL bag, ring at the mouth — the rigid hoop is the whole product.*
|
||||
|
||||
> Hospital-grade vomit bags whose entire design is one detail: a rigid plastic ring at the mouth that holds the bag open without anyone needing to grip it. That detail is the difference between a usable object and an empty grocery sack and a prayer.
|
||||
|
||||
Most "vomit bag" solutions in a household are improvised: a kitchen sack, a paper bowl, the towel within reach. They all share the same failure mode — they collapse the moment whoever's holding them lets go, which is exactly when they need not to. The Valchoose bags are the same units hospitals issue: a deep blue 1000mL pouch heat-sealed onto a rigid white plastic ring that holds itself open, hands-free, indefinitely.
|
||||
|
||||
That detail matters when the user is a kid in a car seat, a passenger in the dark, or an adult mid-wave of nausea — the bag stays where you point it without grip strength or attention. After use, the ring has an eyelet groove cut into the rim: twist the bag closed and lock the twist into the ring itself. The result is a sealed, contained, leak-resistant unit you can hand off or set down without it spilling.
|
||||
|
||||
Compact enough for a glove box, a purse, a bedside drawer, or every guest bath. Fourteen in a pack is roughly the right multiple for "one kept everywhere I might wish I had one" — the kind of preparedness object that's invisible the 99% of the time you don't need it, and the only thing you want the 1% of the time you do.
|
||||
|
||||
Honest limitation: single-use disposable plastic. No thoughtful-material angle here — the trade is "one moment when it works perfectly" against another single-use object in the world. For something needed maybe once a year per household, it's the right side of the line, but worth naming.
|
||||
@@ -5,15 +5,19 @@ import altTab from '../content/reviews/alt-tab/hero.jpg';
|
||||
import apex from '../content/reviews/apex-markdown-processor/hero.png';
|
||||
import blip from '../content/reviews/blip/hero.jpg';
|
||||
import esproP7 from '../content/reviews/espro-p7-french-press/hero.jpg';
|
||||
import finalist from '../content/reviews/finalist/hero.png';
|
||||
import folderPeek from '../content/reviews/folder-peek/screenshot.png';
|
||||
import gumShell from '../content/reviews/gum-shell/hero.png';
|
||||
import hyperkey from '../content/reviews/hyperkey/hero.png';
|
||||
import ice from '../content/reviews/ice/ice-bar.png';
|
||||
import macMouseFix from '../content/reviews/mac-mouse-fix/studio-display.png';
|
||||
import monitorcontrol from '../content/reviews/monitorcontrol/hero.png';
|
||||
import smartmoverShortcut from '../content/reviews/smartmover-shortcut/hero.jpg';
|
||||
import superkey from '../content/reviews/superkey/hero.jpg';
|
||||
import tablepro from '../content/reviews/tablepro/hero.png';
|
||||
import timelined from '../content/reviews/timelined/hero.png';
|
||||
import tot from '../content/reviews/tot/seven-dots.png';
|
||||
import valchooseBarfBags from '../content/reviews/valchoose-barf-bags/hero.jpg';
|
||||
|
||||
import antinote from '../content/reviews/antinote/swipe.mp4';
|
||||
import cotypist from '../content/reviews/cotypist/hero.mp4';
|
||||
@@ -34,15 +38,19 @@ export const heroes: Record<string, Hero | undefined> = {
|
||||
'apex-markdown-processor': { kind: 'image', src: apex },
|
||||
blip: { kind: 'image', src: blip },
|
||||
'espro-p7-french-press': { kind: 'image', src: esproP7 },
|
||||
finalist: { kind: 'image', src: finalist },
|
||||
'folder-peek': { kind: 'image', src: folderPeek },
|
||||
'gum-shell': { kind: 'image', src: gumShell },
|
||||
hyperkey: { kind: 'image', src: hyperkey },
|
||||
ice: { kind: 'image', src: ice },
|
||||
'mac-mouse-fix': { kind: 'image', src: macMouseFix },
|
||||
monitorcontrol: { kind: 'image', src: monitorcontrol },
|
||||
'smartmover-shortcut': { kind: 'image', src: smartmoverShortcut },
|
||||
superkey: { kind: 'image', src: superkey },
|
||||
tablepro: { kind: 'image', src: tablepro },
|
||||
timelined: { kind: 'image', src: timelined },
|
||||
tot: { kind: 'image', src: tot },
|
||||
'valchoose-barf-bags': { kind: 'image', src: valchooseBarfBags },
|
||||
antinote: { kind: 'video', src: antinote },
|
||||
cotypist: { kind: 'video', src: cotypist },
|
||||
dockdoor: { kind: 'video', src: dockdoor },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import EntryList from '../../components/EntryList.astro';
|
||||
import EntryGrid from '../../components/EntryGrid.astro';
|
||||
import { getAllEntries } from '../../lib/entries';
|
||||
import {
|
||||
buildOgImage,
|
||||
@@ -61,7 +61,7 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
ogImage={ogImage}
|
||||
jsonLd={[collectionPage, itemList, breadcrumbs]}
|
||||
>
|
||||
<EntryList entries={entries} />
|
||||
<EntryGrid entries={entries} />
|
||||
<p class="back"><a href="/">← back</a></p>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import SourceBadge from '../../components/SourceBadge.astro';
|
||||
import EditAction from '../../components/EditAction.astro';
|
||||
import { sourceSlug } from '../../lib/sources';
|
||||
import {
|
||||
resolveExternalLink,
|
||||
AFFILIATE_REL,
|
||||
@@ -75,6 +75,9 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
{ name: review.data.category, url: `/categories/${review.data.category}/` },
|
||||
{ name: review.data.name, url: `/reviews/${review.id}/` },
|
||||
]);
|
||||
|
||||
const categoryLabel =
|
||||
review.data.category.charAt(0).toUpperCase() + review.data.category.slice(1);
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
@@ -91,49 +94,27 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
jsonLd={[reviewSchema, breadcrumbs]}
|
||||
>
|
||||
<article>
|
||||
<header class="review-header">
|
||||
{review.data.source && (
|
||||
<p class="source-line"><SourceBadge source={review.data.source} /></p>
|
||||
)}
|
||||
<p class="meta">
|
||||
<a href={`/categories/${review.data.category}/`} class="category">
|
||||
{review.data.category}
|
||||
</a>
|
||||
{
|
||||
external && (
|
||||
<>
|
||||
<span class="dot">·</span>
|
||||
<a
|
||||
href={external.href}
|
||||
target="_blank"
|
||||
rel={external.isAffiliate ? AFFILIATE_REL : 'noopener noreferrer'}
|
||||
>
|
||||
{linkLabel} ↗
|
||||
</a>
|
||||
{external.isAffiliate && <span class="aff-badge">Affiliate</span>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</p>
|
||||
{external?.isAffiliate && (
|
||||
<p class="aff-disclosure">{AFFILIATE_DISCLOSURE}</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{fromFindItem && (
|
||||
<p class="from-find">
|
||||
← First surfaced on{' '}
|
||||
<time datetime={fromFindItem.data.date.toISOString()}>
|
||||
{fmt.format(fromFindItem.data.date)}
|
||||
</time>{' '}
|
||||
as <a href={`/find/${fromFindItem.id}/`}>a find</a>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div class="body">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
{external && (
|
||||
<p class="external">
|
||||
<a
|
||||
href={external.href}
|
||||
target="_blank"
|
||||
rel={external.isAffiliate ? AFFILIATE_REL : 'noopener noreferrer'}
|
||||
>
|
||||
{linkLabel} ↗
|
||||
</a>
|
||||
{external.isAffiliate && <span class="aff-badge">Affiliate</span>}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{external?.isAffiliate && (
|
||||
<p class="aff-disclosure">{AFFILIATE_DISCLOSURE}</p>
|
||||
)}
|
||||
|
||||
{import.meta.env.DEV && (
|
||||
<div class="edit-toolbar">
|
||||
<EditAction
|
||||
@@ -145,61 +126,50 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
</div>
|
||||
)}
|
||||
|
||||
{
|
||||
review.data.tags.length > 0 && (
|
||||
<footer class="tags">
|
||||
{review.data.tags.map((tag) => (
|
||||
<a href={`/tags/${tag}/`} class="tag">#{tag}</a>
|
||||
))}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
<aside class="colophon" aria-label="Colophon">
|
||||
<dl>
|
||||
{review.data.source && (
|
||||
<>
|
||||
<dt>Via</dt>
|
||||
<dd>
|
||||
<a href={`/sources/#${sourceSlug(review.data.source)}`}>
|
||||
{review.data.source}
|
||||
</a>
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
<dt>Filed under</dt>
|
||||
<dd>
|
||||
<a href={`/categories/${review.data.category}/`}>{categoryLabel}</a>
|
||||
</dd>
|
||||
{fromFindItem && (
|
||||
<>
|
||||
<dt>First surfaced</dt>
|
||||
<dd>
|
||||
<time datetime={fromFindItem.data.date.toISOString()}>
|
||||
{fmt.format(fromFindItem.data.date)}
|
||||
</time>
|
||||
{' '}as <a href={`/find/${fromFindItem.id}/`}>a find</a>
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
{review.data.tags.length > 0 && (
|
||||
<>
|
||||
<dt>Tagged</dt>
|
||||
<dd class="colophon-tags">
|
||||
{review.data.tags.map((tag) => (
|
||||
<a href={`/tags/${tag}/`}>#{tag}</a>
|
||||
))}
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
</aside>
|
||||
|
||||
<p class="back"><a href="/">← back</a></p>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.source-line { margin: 0 0 0.5rem; }
|
||||
.from-find {
|
||||
margin: 0 0 1.5rem;
|
||||
padding: 0.6rem 0.85rem;
|
||||
background: var(--rule);
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.from-find time { color: var(--muted); }
|
||||
.review-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.meta {
|
||||
margin: 0.5rem 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.meta .dot { margin: 0 0.4rem; }
|
||||
.meta .category { color: var(--muted); }
|
||||
.meta .category:hover { color: var(--accent); }
|
||||
.aff-badge {
|
||||
display: inline-block;
|
||||
margin-left: 0.45rem;
|
||||
padding: 0.12rem 0.45rem;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
background: var(--rule);
|
||||
border-radius: 999px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.aff-disclosure {
|
||||
margin: 0.6rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
.body :global(p) { margin: 0 0 1rem; }
|
||||
.body :global(h2) { font-size: 1.3rem; margin: 2rem 0 0.75rem; }
|
||||
.body :global(h3) { font-size: 1.1rem; margin: 1.5rem 0 0.5rem; }
|
||||
@@ -299,16 +269,6 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
margin: 0 0 1.75rem;
|
||||
}
|
||||
.body :global(blockquote p) { margin: 0; }
|
||||
.tags {
|
||||
margin-top: 2.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tag { font-size: 0.85rem; color: var(--muted); }
|
||||
.tag:hover { color: var(--accent); }
|
||||
.back { margin-top: 2rem; font-size: 0.9rem; }
|
||||
.edit-toolbar {
|
||||
margin: 2rem 0 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import EntryList from '../../components/EntryList.astro';
|
||||
import EntryGrid from '../../components/EntryGrid.astro';
|
||||
import { getAllEntries } from '../../lib/entries';
|
||||
import {
|
||||
buildOgImage,
|
||||
@@ -61,7 +61,7 @@ const breadcrumbs = buildBreadcrumbs([
|
||||
ogImage={ogImage}
|
||||
jsonLd={[collectionPage, itemList, breadcrumbs]}
|
||||
>
|
||||
<EntryList entries={entries} />
|
||||
<EntryGrid entries={entries} />
|
||||
<p class="back"><a href="/">← back</a></p>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user