diff --git a/NIP.md b/NIP.md
index d4cb8996..7c0a1c53 100644
--- a/NIP.md
+++ b/NIP.md
@@ -18,6 +18,7 @@ These event kinds were created by community contributors and are supported by Di
| Kind | Name | Description | Spec |
|-------|------------------------|------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| 2473 | Bird Detection | Bird-by-ear observation log (species heard in the wild) | [NIP](https://gitlab.com/alexgleason/birdstar/-/blob/main/NIP.md) |
+| 12473 | Birdex | Author's cumulative life list of confirmed bird species | [NIP](https://gitlab.com/alexgleason/birdstar/-/blob/main/NIP.md) |
| 3367 | Color Moment | Color palette post expressing a mood | [NIP](https://gitlab.com/chad.curtis/espy/-/blob/main/NIP.md) |
| 4223 | Weather Reading | Sensor readings from a weather station | [Draft NIP](https://github.com/nostr-protocol/nips/pull/2163) |
| 7516 | Found Log | Log entry recording a user finding a geocache | [NIP-GC](https://gitlab.com/chad.curtis/treasures/-/blob/main/NIP-GC.md) |
@@ -427,7 +428,7 @@ The following specifications are maintained by their respective authors. Ditto i
Color palette posts capturing 3-6 colors from a beautiful moment, optionally accompanied by an emoji and layout preference. Supports horizontal, vertical, grid, star, checkerboard, and diagonal stripe layouts. A form of pre-verbal visual communication through color and emotion.
-### Birdstar (Kinds 2473, 30621)
+### Birdstar (Kinds 2473, 12473, 30621)
**Author:** Alex Gleason
**Spec:** https://gitlab.com/alexgleason/birdstar/-/blob/main/NIP.md
@@ -436,6 +437,7 @@ Color palette posts capturing 3-6 colors from a beautiful moment, optionally acc
Birdstar merges Birdsong Spotter (a bird-by-ear checklist) and Starpoint (an interactive sky map with community constellations) into a single client.
- **Kind 2473 — Bird Detection.** A regular event representing a single identified bird observation. The species is identified by a NIP-73 `i`/`k` pair pointing at the species' Wikidata entity URI (e.g. `https://www.wikidata.org/entity/Q26825` for the American Robin). The `content` field holds an optional freeform human note about the detection. Required tags: NIP-31 `alt`, NIP-73 `i` (Wikidata URL) + `k` (`web`). Ditto renders detections as a species card with the Wikipedia thumbnail, common/scientific name, and article summary.
+- **Kind 12473 — Birdex.** A replaceable event (one per author) indexing every distinct species the author has ever confirmed via kind 2473. Each species is a positional `i`/`n` pair — the Wikidata entity URI followed immediately by the scientific binomial name — emitted in chronological order of first detection. Ditto renders a Birdex as a tiled grid of species, each tile showing the Wikipedia thumbnail with the common name overlaid. In feeds, only the most recent few tiles are shown with a "+N" capstone mirroring how kind 3 follow lists preview members; the post-detail page shows every species.
- **Kind 30621 — Custom Constellation.** An addressable event (`d` tag) representing a single user-drawn star figure. Each `edge` tag (`["edge", from, to]`) references two Hipparcos catalog numbers as decimal strings — e.g. `["edge", "32349", "37279"]` for Sirius → Procyon. Required tags: `d`, `title`, `alt`, and at least one valid `edge`. The `content` field is a freeform description. Ditto renders constellations as a stylized SVG star-map (gnomonically projected onto a tangent plane at the figure's centroid, with stars sized by magnitude) using a bundled Hipparcos catalog that is code-split so the data only loads when a constellation is actually viewed.
### Geocaching (Kinds 37516, 7516)
diff --git a/src/App.tsx b/src/App.tsx
index f5c80a90..550e8568 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -114,6 +114,7 @@ const hardcodedConfig: AppConfig = {
feedIncludeBlobbi: true,
showBirdstar: true,
feedIncludeBirdDetections: true,
+ feedIncludeBirdex: true,
feedIncludeConstellations: true,
followsFeedShowReplies: true,
},
diff --git a/src/components/BirdexContent.tsx b/src/components/BirdexContent.tsx
new file mode 100644
index 00000000..c91e0877
--- /dev/null
+++ b/src/components/BirdexContent.tsx
@@ -0,0 +1,139 @@
+import { useMemo } from 'react';
+import { Bird } from 'lucide-react';
+import type { NostrEvent } from '@nostrify/nostrify';
+
+import { BirdexTile } from '@/components/BirdexTile';
+import { parseBirdexEvent } from '@/lib/parseBirdex';
+import { cn } from '@/lib/utils';
+
+/**
+ * Birdstar kind 12473 — Birdex (life list).
+ *
+ * A replaceable per-author index of every distinct bird species the
+ * author has ever logged via kind 2473. Each species is a positional
+ * `i`/`n` pair (Wikidata entity URI + scientific name), emitted in
+ * chronological order of first detection.
+ *
+ * Feed variant: a small tiled preview of the most recently-added
+ * species plus a "+N" capstone, mirroring how kind 3 follow lists
+ * render as a compact avatar stack with a "+N more" suffix. Full
+ * variant: the whole life list laid out as a responsive grid so
+ * visitors can browse every species the author has ever seen.
+ */
+
+/** Tiles rendered in the compact feed preview before collapsing into "+N". */
+const FEED_PREVIEW_LIMIT = 8;
+
+interface BirdexContentProps {
+ event: NostrEvent;
+ /**
+ * When true, render every species on the life list instead of the
+ * truncated feed preview. Used on the post-detail page.
+ */
+ expanded?: boolean;
+ className?: string;
+}
+
+export function BirdexContent({ event, expanded, className }: BirdexContentProps) {
+ const entries = useMemo(() => parseBirdexEvent(event), [event]);
+
+ // Empty Birdex — either a malformed event or a newly-published
+ // placeholder. Render a minimal dashed card so the feed row still
+ // has a meaningful anchor.
+ if (entries.length === 0) {
+ return (
+
+
+ Empty Birdex — no confirmed species yet.
+
+ );
+ }
+
+ if (expanded) {
+ return (
+
+
+
+
+ Birdex
+
+ {entries.length} species
+
+
+
+
+
+ {entries.map((entry) => (
+
+ ))}
+
+
+ );
+ }
+
+ // Feed variant — show the *most recent* species (tail of the list)
+ // so the preview reflects the author's latest additions, with an
+ // overflow capstone on the final tile when the Birdex is larger
+ // than the preview. The capstone displaces one species slot, so
+ // when overflowing we render (LIMIT - 1) real tiles + the capstone;
+ // the capstone's count is "species not shown", which includes the
+ // one species the capstone itself displaced.
+ const overflowing = entries.length > FEED_PREVIEW_LIMIT;
+ const visibleSpeciesCount = overflowing ? FEED_PREVIEW_LIMIT - 1 : entries.length;
+ const previewEntries = entries.slice(-visibleSpeciesCount);
+ const overflowCount = entries.length - visibleSpeciesCount;
+
+ return (
+
+ );
+}
+
+/**
+ * Final capstone tile that reads "+N" when the life list overflows
+ * the feed preview. Mirrors the "+N more" suffix on kind 3 follow-list
+ * avatar stacks.
+ */
+function OverflowTile({ count }: { count: number }) {
+ return (
+
+
+ +{count}
+
+
+ );
+}
diff --git a/src/components/BirdexTile.tsx b/src/components/BirdexTile.tsx
new file mode 100644
index 00000000..67444bf1
--- /dev/null
+++ b/src/components/BirdexTile.tsx
@@ -0,0 +1,114 @@
+import { Bird } from 'lucide-react';
+import { Link } from 'react-router-dom';
+
+import { Skeleton } from '@/components/ui/skeleton';
+import { useWikidataEntity } from '@/hooks/useWikidataEntity';
+import { useWikipediaSummary } from '@/hooks/useWikipediaSummary';
+import { sanitizeUrl } from '@/lib/sanitizeUrl';
+import { cn } from '@/lib/utils';
+
+/**
+ * A single tile in a Birdex grid — one species.
+ *
+ * Resolves Wikidata → English Wikipedia to pull a thumbnail and common
+ * name. The scientific name (optional, from the paired `n` tag on the
+ * Birdex event) is used as a fallback label while the remote fetch is
+ * in flight or fails.
+ *
+ * Clicking the tile routes to Ditto's external-content page for the
+ * species' Wikidata URL, so the species page aggregates detections,
+ * comments, and other Birdex authors who have this species on their
+ * life lists — the same landing spot used by kind 2473 bird-detection
+ * cards.
+ */
+interface BirdexTileProps {
+ entityUri: string;
+ entityId: string;
+ /** Optional scientific name from the paired `n` tag. */
+ scientificName?: string;
+ /** Extra classes applied to the tile container. */
+ className?: string;
+ /** Drop the navigation link (used by disabled-hover embeds). */
+ nonInteractive?: boolean;
+}
+
+export function BirdexTile({
+ entityUri,
+ entityId,
+ scientificName,
+ className,
+ nonInteractive,
+}: BirdexTileProps) {
+ const { data: entity, isLoading: entityLoading } = useWikidataEntity(entityId);
+ const wikipediaTitle = entity?.wikipediaTitle ?? null;
+ const { data: summary, isLoading: summaryLoading } = useWikipediaSummary(wikipediaTitle);
+
+ const isLoading = entityLoading || summaryLoading;
+
+ // Prefer the Wikipedia page title for the display label; fall back to
+ // the scientific name from the Birdex's `n` tag while fetches are in
+ // flight or when no English article exists.
+ const commonName = summary?.title ?? (scientificName || 'Unknown species');
+ const thumbnail = sanitizeUrl(summary?.thumbnail?.source);
+
+ const inner = (
+