+ )}
diff --git a/src/pages/podcast/feed.xml.ts b/src/pages/podcast/feed.xml.ts
new file mode 100644
index 0000000..2d28282
--- /dev/null
+++ b/src/pages/podcast/feed.xml.ts
@@ -0,0 +1,54 @@
+import rss from '@astrojs/rss';
+import type { APIContext } from 'astro';
+import { getCollection } from 'astro:content';
+import { site } from '../../site.config';
+
+const AUDIO_TYPES: Record = {
+ mp3: 'audio/mpeg',
+ m4a: 'audio/mp4',
+ ogg: 'audio/ogg',
+ opus: 'audio/opus',
+ wav: 'audio/wav',
+};
+
+function audioType(url: string): string {
+ const extension = new URL(url).pathname.split('.').pop()?.toLowerCase() ?? '';
+ return AUDIO_TYPES[extension] ?? 'audio/mpeg';
+}
+
+export async function GET(context: APIContext) {
+ const episodes = (
+ await getCollection('episodes', ({ data }) => !data.draft && Boolean(data.audioUrl))
+ ).sort((a, b) => b.data.published.getTime() - a.data.published.getTime());
+
+ return rss({
+ title: `${site.title} — the podcast`,
+ description:
+ 'Who gets to act in our name? Conversations about authority, delegation, recovery, and responsibility, with full transcripts published beside every episode.',
+ site: new URL('/podcast', context.site).href,
+ xmlns: {
+ itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd',
+ },
+ customData: [
+ 'en',
+ `${site.author.name}`,
+ 'false',
+ `${new URL('/podcast', context.site).href}`,
+ ].join(''),
+ items: episodes.map((entry) => ({
+ title: entry.data.title,
+ description: entry.data.description,
+ link: `/podcast/${entry.id}`,
+ pubDate: entry.data.published,
+ enclosure: {
+ url: entry.data.audioUrl!,
+ length: entry.data.audioBytes ?? 0,
+ type: audioType(entry.data.audioUrl!),
+ },
+ customData: [
+ `${entry.data.episode}`,
+ entry.data.duration ? `${entry.data.duration}` : '',
+ ].join(''),
+ })),
+ });
+}
diff --git a/src/pages/podcast/index.astro b/src/pages/podcast/index.astro
index 741b4a2..314fa62 100644
--- a/src/pages/podcast/index.astro
+++ b/src/pages/podcast/index.astro
@@ -2,8 +2,10 @@
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { formatDate, newestFirst } from '../../lib/content';
+import { site } from '../../site.config';
const entries = newestFirst(await getCollection('episodes', ({ data }) => !data.draft));
+const feedUrl = new URL(site.podcastFeedUrl, Astro.site).href;
---
@@ -19,21 +21,47 @@ const entries = newestFirst(await getCollection('episodes', ({ data }) => !data.
{Array.from({ length: 21 }).map((_, index) => )}
+
+
+
+
Subscribe
+
+ Paste the feed into any podcast app. New episodes arrive there the moment they are published here —
+ no platform account between you and the audio.
+
The archive is ready for real episodes and transcripts. No synthetic placeholder episode has been published.
}
+ )) : (
+
+
+ The archive is empty because no episode has been recorded yet — this publication does not ship
+ synthetic placeholders. The feed above is already live: subscribe now and episode one will simply appear.
+