Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,18 @@ LS_FOUNDING_PAYMENT_URL=

# Owner emails for /admin (comma-separated; M13). Gates the manual plan flag.
OWNER_EMAILS=

# --- WP-09 (marketing + free tools + admin + feedback) ---

# Global daily spend cap for the free public tools (USD, UTC day). Default 3.
FREE_TOOL_DAILY_USD=

# PostHog public key (feedback + product events). Absent → events are skipped silently.
NEXT_PUBLIC_POSTHOG_KEY=
# PostHog host if you use the EU cloud (default https://us.i.posthog.com).
POSTHOG_HOST=

# Resend key: feedback notifications to the owner. Absent → seam logged, no email.
RESEND_API_KEY=
# Feedback/notification sender (must be a verified Resend identity).
FEEDBACK_FROM_EMAIL=EpisodeKit <onboarding@resend.dev>
2 changes: 2 additions & 0 deletions web/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { UserButton } from "@clerk/nextjs";
import { AppHeader } from "@/components/app/app-header";
import { FeedbackWidget } from "@/components/app/feedback-widget";
import { requireUser } from "@/lib/auth";
import { isClerkConfigured } from "@/lib/env";
import { getShowByOwnerSafe } from "@/lib/show/queries-safe";
Expand All @@ -14,6 +15,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod
userButton={isClerkConfigured() ? <UserButton /> : null}
/>
<main className="mx-auto w-full max-w-3xl flex-1 px-4 py-8">{children}</main>
<FeedbackWidget />
</div>
);
}
196 changes: 196 additions & 0 deletions web/app/(marketing)/compare/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// /compare/podsqueeze + /compare/castmagic — ported from the legacy pages at
// the same paths. Product rows updated to the MVP facts (2 episodes free,
// $19 Pro, 7-artifact package + free tools); unverifiable third-party claims
// (Reddit attributions) removed per §0.5. Logged in DECISIONS.md.
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { COMPARE_SLUGS } from "@/lib/marketing/routes";

export function generateStaticParams(): Array<{ slug: (typeof COMPARE_SLUGS)[number] }> {
return COMPARE_SLUGS.map((slug) => ({ slug }));
}

interface CompareContent {
title: string;
metaDescription: string;
sub: string;
rows: Array<[string, string, string]>;
chooseThem: { h: string; p: React.ReactNode };
chooseUs: { h: string; p: React.ReactNode };
fact: string;
}

const US_INTRO = (
<>
EpisodeKit: Free (2 episodes/mo) · Pro $19/mo. Show notes, chapters, summary, titles, blog
post, newsletter and social posts from one episode — plus free no-signup tools for
discussion kits and email series. Every quote is copied word-for-word from your transcript.
</>
);

const CONTENT: Record<(typeof COMPARE_SLUGS)[number], CompareContent> = {
podsqueeze: {
title: "EpisodeKit vs Podsqueeze — honest comparison (Oct 2026)",
metaDescription:
"EpisodeKit vs Podsqueeze for podcast show notes and content repurposing: pricing, approach, verbatim-quote integrity, and who each is right for. Facts as of October 2026.",
sub: "Both turn podcast episodes into show notes and repurposed content. Here's the honest breakdown — including where Podsqueeze is the better choice.",
rows: [
["Entry price", "Free (2 episodes/mo) · Pro $19/mo flat", "From ~$9/mo, tiered by uploads (see their pricing page)"],
[
"What it makes",
"Show notes, chapters, summary, titles, blog post, newsletter, social posts — plus free no-signup tools for discussion kits and email series",
"Show notes, clips, social posts, transcripts — a broad feature set",
],
[
"Quote integrity",
"Verbatim-only: every quote in every artifact is an exact sentence from your transcript; paraphrased quotes are rejected automatically",
"Standard AI generation",
],
[
"Works without an account",
"Yes — free tools (show notes, titles, discussion kit, email series) with no signup",
"No — account required",
],
["Audio upload", "Yes (browser-compressed) or paste transcript", "Yes, plus RSS import"],
["Video clips", "Not in the MVP", "Yes"],
["Positioning", "Small, sharp, honest — for indie shows and solo creators", "Established all-in-one repurposing suite"],
],
chooseThem: {
h: "Choose Podsqueeze if…",
p: "you want an established all-in-one suite today — especially if video clips and RSS-based episode import are must-haves, or your workflow already lives in their tool. It's a real product that does a lot.",
},
chooseUs: {
h: "Choose EpisodeKit if…",
p: (
<>
you publish show notes, a blog or a newsletter and need artifacts beyond clips — and you
care that every quote shown to your audience is word-for-word from the episode. Try it
with no signup first:{" "}
<Link href="/tools/show-notes" className="font-medium text-accent hover:underline">
Generate show notes free →
</Link>
</>
),
},
fact: "Facts as of October 2026, from each product's public pricing page. Prices change — verify before buying. This page is written by the EpisodeKit maker; we've kept the trade-offs honest because a bad-fit customer churns anyway.",
},
castmagic: {
title: "EpisodeKit vs Castmagic — honest comparison (Oct 2026)",
metaDescription:
"EpisodeKit vs Castmagic for podcast content repurposing: pricing, complexity, verbatim-quote integrity, and who each is right for. Facts as of October 2026.",
sub: "Both generate content from podcast episodes. Castmagic is the bigger, older platform; EpisodeKit is the younger, sharper tool. Honest breakdown below.",
rows: [
["Entry price", "Free (2 episodes/mo) · Pro $19/mo flat", "From ~$19/mo, tiering to enterprise-scale plans (see their pricing page)"],
[
"What it makes",
"Seven artifacts, one flow: show notes, chapters, summary, titles, blog post, newsletter, social posts — plus free no-signup tools",
"Many artifact types, custom prompts, chat, integrations — a large surface",
],
["Learning curve", "One screen per episode, paste and go", "Broader surface — more options to learn"],
[
"Quote integrity",
"Verbatim-only; AI output that paraphrases a quote is rejected automatically",
"Standard AI generation",
],
["Free without signup", "Yes — four single-artifact tools, no account", "Trial with account"],
["Custom prompts / pipelines", "Not in the MVP", "Yes, extensive"],
["Best for", "Indie shows, solo creators, newsletter-driven podcasts", "Production teams that want configurable pipelines and integrations"],
],
chooseThem: {
h: "Choose Castmagic if…",
p: "you're a production team that lives in custom prompts, needs their integration breadth, or already has a workflow built there. Their depth of configuration is real.",
},
chooseUs: {
h: "Choose EpisodeKit if…",
p: (
<>
you want to paste an episode and get the whole package in one pass — and you&apos;ve been
burned by AI tools inventing &ldquo;quotes&rdquo; your guests never said. Ours
architecturally can&apos;t. Start free, no signup:{" "}
<Link href="/tools/show-notes" className="font-medium text-accent hover:underline">
Generate show notes free →
</Link>
</>
),
},
fact: "Facts as of October 2026, from each product's public pricing page. Prices change — verify before buying. Written by the EpisodeKit maker, trade-offs kept honest.",
},
};

export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
if (!isSlug(slug)) return {};
return { title: CONTENT[slug].title, description: CONTENT[slug].metaDescription };
}

function isSlug(value: string): value is (typeof COMPARE_SLUGS)[number] {
return (COMPARE_SLUGS as readonly string[]).includes(value);
}

export default async function ComparePage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
if (!isSlug(slug)) notFound();
const c = CONTENT[slug];
return (
<div className="mx-auto w-full max-w-3xl px-4 py-16">
<h1 className="text-3xl font-semibold leading-tight tracking-tight">
EpisodeKit vs {slug === "podsqueeze" ? "Podsqueeze" : "Castmagic"}
</h1>
<p className="mt-3 text-body">{c.sub}</p>

<div className="mt-6 overflow-x-auto rounded-xl border border-border">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-surface-2 text-left">
<th className="w-44 p-3 text-xs font-medium tracking-wide text-faint uppercase"> </th>
<th className="p-3 text-xs font-medium tracking-wide text-accent uppercase">EpisodeKit</th>
<th className="p-3 text-xs font-medium tracking-wide text-faint uppercase">
{slug === "podsqueeze" ? "Podsqueeze" : "Castmagic"}
</th>
</tr>
</thead>
<tbody>
{c.rows.map(([label, us, them]) => (
<tr key={label} className="border-t border-border align-top">
<td className="p-3 text-faint">{label}</td>
<td className="p-3">{us}</td>
<td className="p-3 text-body">{them}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="mt-2 text-[13px] text-faint">EpisodeKit plans and artifacts as described above: {US_INTRO}</p>

<div className="mt-6 flex flex-col gap-4">
<div className="rounded-xl border border-border p-5">
<h2 className="text-base font-semibold">{c.chooseThem.h}</h2>
<p className="mt-1 text-body">{c.chooseThem.p}</p>
</div>
<div className="rounded-xl border border-border p-5">
<h2 className="text-base font-semibold">{c.chooseUs.h}</h2>
<p className="mt-1 text-body">{c.chooseUs.p}</p>
</div>
</div>

<p className="mt-6 text-[13px] text-faint">{c.fact}</p>

<p className="mt-8 text-sm text-body">
{slug === "podsqueeze" ? (
<Link href="/compare/castmagic" className="text-accent hover:underline">
vs Castmagic →
</Link>
) : (
<Link href="/compare/podsqueeze" className="text-accent hover:underline">
vs Podsqueeze →
</Link>
)}
</p>
</div>
);
}
151 changes: 151 additions & 0 deletions web/app/(marketing)/guides/repurposing/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// /guides/repurposing — ported from site/guides/repurposing.html at the same
// path, links updated to the new routes.
import type { Metadata } from "next";
import Link from "next/link";

export const metadata: Metadata = {
title: "The Podcast Repurposing Playbook (2026) — EpisodeKit",
description:
"How to turn one episode into a month of content without burning out: the priority order, the batch workflow, the verbatim rule, and the tools.",
};

export default function GuideRepurposingPage() {
return (
<div className="mx-auto w-full max-w-2xl px-4 py-16">
<h1 className="text-3xl font-semibold leading-tight tracking-tight">
The Podcast Repurposing Playbook
</h1>
<p className="mt-2.5 text-sm text-faint">
One episode, a month of content — without a content team. The priority order, the batch
workflow, and the rules that keep it honest. ~6 min read.
</p>

<div className="mt-8 flex flex-col gap-7 text-[15px] leading-7 text-body">
<section>
<h2 className="mb-2 text-2xl font-semibold text-foreground">
The math nobody tells solo podcasters
</h2>
<p>
A weekly 45-minute episode contains roughly 6,000 spoken words — more than most people
write in a week. Nearly all of it evaporates. Repurposing isn&apos;t making
&ldquo;extra&rdquo; content; it&apos;s <b>rescuing content you already made</b>. Done
even partially, one episode yields: an episode-page writeup, a newsletter, a 5-day
email drip, 3–6 social posts with quotes, a community discussion prompt, and title
options for the clip cuts. That&apos;s 15–20 assets, most of them derived, not written.
</p>
</section>

<section>
<h2 className="mb-2 text-2xl font-semibold text-foreground">
Priority order (when you can&apos;t do it all)
</h2>
<div className="mt-3 overflow-x-auto rounded-xl border border-border">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-surface-2 text-left text-xs tracking-wide text-accent uppercase">
<th className="p-3">Asset</th>
<th className="p-3">Effort</th>
<th className="p-3">Return</th>
<th className="p-3">Do it if…</th>
</tr>
</thead>
<tbody>
{[
["Show notes / episode page", "Medium", "Compounding (search)", "Always — it's the anchor everything links to"],
["Newsletter issue", "Low", "Highest (owned audience)", "You have any list at all"],
["2–3 quote posts", "Very low", "Reach + guest shares", "Always — guests reshare their own best lines"],
["Community discussion prompts", "Low", "Retention", "You have a Discord/Patreon/group"],
["5-day email drip", "Medium", "Onboarding new subscribers for months", "List > ~500"],
["Video clips", "High", "Volatile", "You have video source + editing time"],
].map(([asset, effort, ret, when]) => (
<tr key={asset} className="border-t border-border align-top">
<td className="p-3">{asset}</td>
<td className="p-3">{effort}</td>
<td className="p-3">{ret}</td>
<td className="p-3">{when}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>

<section>
<h2 className="mb-2 text-2xl font-semibold text-foreground">The verbatim rule</h2>
<div className="mt-3 rounded-xl border-l-[3px] border-accent bg-surface p-4">
Whatever tool writes your repurposed assets:{" "}
<b>quotes must be word-for-word from the transcript.</b> A fabricated &ldquo;quote&rdquo;
shared publicly by a guest is the fastest way to burn a relationship — and it&apos;s the
most common complaint about AI repurposing tools. Our{" "}
<Link href="/tools/show-notes" className="text-accent hover:underline">generator</Link>{" "}
enforces this architecturally: AI-paraphrased quotes are rejected at generation time.
</div>
</section>

<section>
<h2 className="mb-2 text-2xl font-semibold text-foreground">
The batch workflow (one focused hour per episode)
</h2>
<ol className="mt-3 ml-5 list-decimal space-y-2">
<li>
<b>Transcript ready</b> (your usual ASR; or paste it).
</li>
<li>
<b>Pull the 6 best moments</b> — claims, numbers, stories. These become your quotes
and timestamps.
</li>
<li>
<b>Write the notes</b> around them —{" "}
<Link href="/templates/show-notes" className="text-accent hover:underline">template here</Link>,
full anatomy in the{" "}
<Link href="/guides/show-notes" className="text-accent hover:underline">show notes guide</Link>.
</li>
<li>
<b>Derive the rest</b>: newsletter = summary + one quote; social = quote + link;
community = 3 questions; drip = 5 moments, one per day.
</li>
<li>
<b>Schedule once</b>, then leave it alone. Batching beats dripping your own attention
all week.
</li>
</ol>
<p className="mt-3">
Steps 2–4 are exactly what the{" "}
<Link href="/sign-up" className="text-accent hover:underline">EpisodeKit app</Link>{" "}
automates — all seven artifact families from one episode — but the playbook works by
hand too; the tools are speed, not magic.
</p>
</section>

<section>
<h2 className="mb-2 text-2xl font-semibold text-foreground">Choosing tools honestly</h2>
<p>
All-in-one suites (Podsqueeze, Castmagic) are real products with broad feature sets;
the common trade-offs are price tiers and generic-feeling output. Ours trades breadth
for the verbatim guarantee and a no-signup floor. The honest comparisons:{" "}
<Link href="/compare/podsqueeze" className="text-accent hover:underline">vs Podsqueeze</Link>{" "}
·{" "}
<Link href="/compare/castmagic" className="text-accent hover:underline">vs Castmagic</Link>.
</p>
</section>

<section>
<h2 className="mb-2 text-2xl font-semibold text-foreground">The one-metric check</h2>
<p>
Each month, ask: <b>which repurposed asset actually got clicked?</b> Kill the rest
without guilt. The playbook survives contact with your audience only if you prune it.
</p>
<p className="mt-4">
Start with the free{" "}
<Link href="/tools/show-notes" className="text-accent hover:underline">
show notes generator
</Link>{" "}
— paste a transcript, no signup — or grab the{" "}
<Link href="/templates/show-notes" className="text-accent hover:underline">template</Link>{" "}
and do it by hand.
</p>
</section>
</div>
</div>
);
}
Loading
Loading