diff --git a/src/components/BirdDetectionContent.tsx b/src/components/BirdDetectionContent.tsx
index f3684427..6946471e 100644
--- a/src/components/BirdDetectionContent.tsx
+++ b/src/components/BirdDetectionContent.tsx
@@ -1,8 +1,9 @@
import { useMemo } from 'react';
-import { Bird, ExternalLink, MessageCircle } from 'lucide-react';
+import { Bird } from 'lucide-react';
import { Link } from 'react-router-dom';
import type { NostrEvent } from '@nostrify/nostrify';
+import { BirdSongPlayer } from '@/components/BirdSongPlayer';
import { Skeleton } from '@/components/ui/skeleton';
import { useWikidataEntity } from '@/hooks/useWikidataEntity';
import { useWikipediaSummary } from '@/hooks/useWikipediaSummary';
@@ -87,12 +88,11 @@ export function BirdDetectionContent({ event, className }: BirdDetectionContentP
const commonName = summary?.title ?? altSpecies?.common ?? 'Unknown species';
const extract = summary?.extract;
const thumbnail = sanitizeUrl(summary?.thumbnail?.source);
- const articleUrl = sanitizeUrl(summary?.articleUrl);
- // "Discuss" routes the user to Ditto's external-content page for this
- // species' Wikidata URL. Other users' kind 2473 detections and NIP-22
- // comments both attach to the same `i`-tag identifier, so the discussion
- // thread aggregates naturally across clients.
+ // The whole card routes to Ditto's external-content page for this
+ // species' Wikidata URL. Other users' kind 2473 detections and
+ // NIP-22 comments both attach to the same `i`-tag identifier, so
+ // the discussion thread aggregates naturally across clients.
const discussPath = wikidata ? `/i/${encodeURIComponent(wikidata.url)}` : undefined;
// When the user's own freeform note exists we show it above the
@@ -114,93 +114,89 @@ export function BirdDetectionContent({ event, className }: BirdDetectionContentP
return (
+ Heard at {new Date(event.created_at * 1000).toLocaleString()}.
+
+ )}
- {scientificName && (
-
- {scientificName}
-
+
+ {/* Reference recording from Wikipedia/Commons, when available.
+ * `BirdSongPlayer` returns null when the article has no
+ * usable audio, so the right-hand column collapses
+ * cleanly and the text reflows across the full card.
+ * The click handler stops propagation so toggling
+ * playback doesn't also navigate to `/i/...`. */}
+ {wikipediaTitle && (
+
diff --git a/src/components/BirdexChorusButton.tsx b/src/components/BirdexChorusButton.tsx
new file mode 100644
index 00000000..a04d32fe
--- /dev/null
+++ b/src/components/BirdexChorusButton.tsx
@@ -0,0 +1,279 @@
+import {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import { Play } from 'lucide-react';
+
+import { Skeleton } from '@/components/ui/skeleton';
+import { useBirdSong } from '@/hooks/useBirdSong';
+import { useWikidataEntity } from '@/hooks/useWikidataEntity';
+import { cn } from '@/lib/utils';
+
+/**
+ * Chorus play/pause button for a Birdex (kind 12473) life list.
+ *
+ * A single control that fires every species' reference recording from
+ * Wikipedia/Commons *at the same time*, producing an overlapping
+ * dawn-chorus effect. Each recording loops independently so the
+ * chorus sustains until the user hits pause.
+ *
+ * Architecture: the parent owns a single `isPlaying` flag. It renders
+ * one `BirdexChorusVoice` per species, each of which:
+ * 1. Resolves its Wikidata ID → Wikipedia title → Commons audio URL
+ * via the same hooks the tile thumbnails already call (so the
+ * title-resolution round-trips are cache hits).
+ * 2. Owns a hidden `