Initial commit: PodcastYes — AI podcast platform

This commit is contained in:
Leon Serfaty
2026-06-07 03:58:32 -04:00
commit 155507f21a
151 changed files with 19826 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
import { SiteHeader } from "@/components/marketing/site-header";
import { SiteFooter } from "@/components/marketing/site-footer";
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen flex-col">
<SiteHeader />
<main className="flex-1">{children}</main>
<SiteFooter />
</div>
);
}
+242
View File
@@ -0,0 +1,242 @@
import Link from "next/link";
import {
ArrowRight,
FileText,
AudioLines,
ImageIcon,
Sparkles,
Languages,
Users,
Repeat,
ListChecks,
Check,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { PLAN_ORDER, PLANS } from "@/lib/billing/plans";
import { formatPrice } from "@/lib/utils";
export default function LandingPage() {
return (
<>
{/* Hero */}
<section className="relative overflow-hidden bg-hero-wash">
<div className="container flex flex-col items-center gap-7 py-24 text-center md:py-36">
<Badge variant="secondary" className="py-1.5">
<Sparkles className="h-3.5 w-3.5 text-brand" />
GPT-4 · ElevenLabs · DALL·E in one workflow
</Badge>
<h1 className="max-w-4xl text-balance font-display text-5xl font-extrabold leading-[1.05] tracking-tight sm:text-6xl md:text-7xl">
From a topic idea to a{" "}
<span className="text-brand">finished podcast</span> in minutes
</h1>
<p className="max-w-2xl text-lg text-muted-foreground sm:text-xl">
PodcastYes writes the script, records realistic multi-voice audio, and designs the cover
art automatically. No microphone, no editing, no design skills required.
</p>
<div className="mt-2 flex flex-col gap-3 sm:flex-row">
<Button asChild size="lg">
<Link href="/sign-up">
Start free <ArrowRight className="h-4 w-4" />
</Link>
</Button>
<Button asChild size="lg" variant="outline">
<Link href="/#how-it-works">See how it works</Link>
</Button>
</div>
<p className="text-sm text-muted-foreground">
Free plan includes 3 scripts &amp; 1 audio generation / month. No card required.
</p>
</div>
</section>
{/* How it works */}
<section id="how-it-works" className="border-t border-border bg-secondary py-24 md:py-28">
<div className="container">
<SectionHeading
eyebrow="How it works"
title="Three steps to a published episode"
subtitle="Configure once. The AI generates everything. You fine-tune and publish."
/>
<div className="mt-16 grid gap-6 md:grid-cols-3">
{STEPS.map((step, i) => (
<Card key={step.title} className="relative transition-shadow hover:shadow-md">
<CardContent className="space-y-4 p-8">
<div className="flex items-center justify-between">
<span className="flex h-12 w-12 items-center justify-center rounded-2xl bg-brand/10 text-brand">
<step.icon className="h-6 w-6" />
</span>
<span className="font-display text-5xl font-extrabold text-foreground/[0.08]">
{String(i + 1).padStart(2, "0")}
</span>
</div>
<h3 className="font-display text-xl font-bold tracking-tight">{step.title}</h3>
<p className="text-muted-foreground">{step.body}</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Features */}
<section id="features" className="py-24 md:py-28">
<div className="container">
<SectionHeading
eyebrow="Everything in one place"
title="The whole podcast toolkit"
subtitle="Replace a writer, a voice actor, an editor, and a designer with one workflow."
/>
<div className="mt-16 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{FEATURES.map((f) => (
<div
key={f.title}
className="flex gap-4 rounded-2xl border border-border bg-card p-6 transition-all hover:-translate-y-0.5 hover:shadow-md"
>
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-brand/10 text-brand">
<f.icon className="h-5 w-5" />
</span>
<div className="space-y-1">
<h3 className="font-display font-bold tracking-tight">{f.title}</h3>
<p className="text-sm text-muted-foreground">{f.body}</p>
</div>
</div>
))}
</div>
</div>
</section>
{/* Pricing preview */}
<section id="pricing" className="border-t border-border bg-secondary py-24 md:py-28">
<div className="container">
<SectionHeading
eyebrow="Pricing"
title="Start free, upgrade as you grow"
subtitle="Simple monthly plans. Cancel anytime."
/>
<div className="mt-16 grid gap-6 lg:grid-cols-4">
{PLAN_ORDER.map((key) => {
const plan = PLANS[key];
return (
<Card
key={key}
className={
plan.highlight
? "relative ring-2 ring-brand shadow-lg"
: "relative"
}
>
{plan.highlight && (
<Badge variant="brand" className="absolute -top-3 left-1/2 -translate-x-1/2 bg-brand text-brand-foreground shadow-sm">
Most popular
</Badge>
)}
<CardContent className="space-y-6 p-7">
<div>
<h3 className="font-display text-lg font-bold tracking-tight">{plan.name}</h3>
<p className="mt-1 text-sm text-muted-foreground">{plan.tagline}</p>
</div>
<div className="flex items-baseline gap-1">
<span className="font-display text-4xl font-extrabold tracking-tight">{formatPrice(plan.priceMonthly)}</span>
<span className="text-sm text-muted-foreground">/mo</span>
</div>
<Button
asChild
className="w-full"
variant={plan.highlight ? "default" : "outline"}
>
<Link href="/sign-up">{key === "free" ? "Start free" : `Choose ${plan.name}`}</Link>
</Button>
<ul className="space-y-2.5 text-sm">
{plan.bullets.slice(0, 5).map((b) => (
<li key={b} className="flex gap-2.5">
<Check className="mt-0.5 h-4 w-4 shrink-0 text-brand" />
<span className="text-muted-foreground">{b}</span>
</li>
))}
</ul>
</CardContent>
</Card>
);
})}
</div>
<p className="mt-10 text-center text-sm text-muted-foreground">
Full comparison on the{" "}
<Link href="/pricing" className="font-semibold text-brand hover:underline">
pricing page
</Link>
.
</p>
</div>
</section>
{/* CTA */}
<section className="py-24 md:py-28">
<div className="container">
<div className="relative overflow-hidden rounded-3xl bg-primary px-8 py-20 text-center text-primary-foreground">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(60%_80%_at_50%_0%,hsl(var(--brand)/0.35),transparent_70%)]" />
<div className="relative">
<h2 className="font-display text-4xl font-extrabold tracking-tight sm:text-5xl">
Make your first episode today
</h2>
<p className="mx-auto mt-4 max-w-xl text-lg text-primary-foreground/75">
Spin up a fully produced episode on the free plan in a couple of minutes then decide.
</p>
<Button asChild size="lg" variant="brand" className="mt-9">
<Link href="/sign-up">
Get started free <ArrowRight className="h-4 w-4" />
</Link>
</Button>
</div>
</div>
</div>
</section>
</>
);
}
function SectionHeading({
eyebrow,
title,
subtitle,
}: {
eyebrow: string;
title: string;
subtitle: string;
}) {
return (
<div className="mx-auto max-w-2xl text-center">
<p className="text-[13px] font-semibold uppercase tracking-[0.04em] text-brand">{eyebrow}</p>
<h2 className="mt-3 font-display text-3xl font-extrabold tracking-tight sm:text-4xl md:text-5xl">{title}</h2>
<p className="mt-4 text-lg text-muted-foreground">{subtitle}</p>
</div>
);
}
const STEPS = [
{
icon: FileText,
title: "Configure your episode",
body: "Set the topic, tone, length, audience, and language. Pick a format: solo, interview, or multi-host.",
},
{
icon: AudioLines,
title: "AI generates everything",
body: "GPT-4 writes the script, ElevenLabs records it with realistic voices, and DALL·E designs the cover art.",
},
{
icon: ImageIcon,
title: "Fine-tune & publish",
body: "Edit the script, regenerate sections, download the MP3 and assets, and repurpose into blogs and social posts.",
},
];
const FEATURES = [
{ icon: FileText, title: "AI script generation", body: "Structured, on-brand scripts tailored to your tone, format, and audience." },
{ icon: AudioLines, title: "Multi-voice audio", body: "14+ realistic voices and multi-speaker dialogue for interviews and panels." },
{ icon: ImageIcon, title: "AI cover art", body: "Eye-catching episode artwork generated to match your topic in one click." },
{ icon: Repeat, title: "Content repurposing", body: "Turn any episode into blog posts, social threads, and newsletters instantly." },
{ icon: ListChecks, title: "Series generator", body: "Plan an entire season — titles, topics, and episodes — from a single prompt." },
{ icon: Languages, title: "13+ languages", body: "Produce episodes for a global audience without re-recording anything." },
];
+76
View File
@@ -0,0 +1,76 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Check } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { PLAN_ORDER, PLANS } from "@/lib/billing/plans";
import { formatPrice } from "@/lib/utils";
export const metadata: Metadata = {
title: "Pricing",
description: "Simple plans for every podcaster — start free and upgrade as you grow.",
};
export default function PricingPage() {
return (
<div className="bg-hero-wash">
<div className="container py-24 md:py-28">
<div className="mx-auto max-w-2xl text-center">
<p className="text-[13px] font-semibold uppercase tracking-[0.04em] text-brand">Pricing</p>
<h1 className="mt-3 font-display text-5xl font-extrabold tracking-tight md:text-6xl">
Start free, scale anytime
</h1>
<p className="mt-4 text-lg text-muted-foreground">
Upgrade for higher limits and more features. Cancel anytime. Pay with Stripe
or PayPal.
</p>
</div>
<div className="mt-16 grid gap-6 lg:grid-cols-4">
{PLAN_ORDER.map((key) => {
const plan = PLANS[key];
return (
<Card
key={key}
className={plan.highlight ? "relative ring-2 ring-brand shadow-lg" : "relative"}
>
{plan.highlight && (
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2 bg-brand text-brand-foreground shadow-sm">
Most popular
</Badge>
)}
<CardContent className="flex h-full flex-col gap-6 p-7">
<div>
<h3 className="font-display text-lg font-bold tracking-tight">{plan.name}</h3>
<p className="mt-1 text-sm text-muted-foreground">{plan.tagline}</p>
</div>
<div className="flex items-baseline gap-1">
<span className="font-display text-5xl font-extrabold tracking-tight">{formatPrice(plan.priceMonthly)}</span>
<span className="text-sm text-muted-foreground">/mo</span>
</div>
<Button asChild className="w-full" variant={plan.highlight ? "default" : "outline"}>
<Link href="/sign-up">{key === "free" ? "Start free" : `Choose ${plan.name}`}</Link>
</Button>
<ul className="space-y-2.5 text-sm">
{plan.bullets.map((b) => (
<li key={b} className="flex gap-2.5">
<Check className="mt-0.5 h-4 w-4 shrink-0 text-brand" />
<span className="text-muted-foreground">{b}</span>
</li>
))}
</ul>
</CardContent>
</Card>
);
})}
</div>
<p className="mx-auto mt-12 max-w-2xl text-center text-xs text-muted-foreground">
Prices in USD. Annual billing saves roughly two months. Usage limits reset on the first of
each month.
</p>
</div>
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
import type { Metadata } from "next";
export const metadata: Metadata = { title: "Privacy Policy" };
export default function PrivacyPage() {
return (
<div className="container max-w-3xl py-20 md:py-24">
<h1 className="font-display text-4xl font-extrabold tracking-tight md:text-5xl">Privacy Policy</h1>
<p className="mt-4 text-muted-foreground">
We collect the account information you provide (name, email) and the content you create to
operate PodcastYes. Episode prompts are sent to our AI providers (OpenAI and ElevenLabs) to
generate scripts, audio, and artwork. Generated assets are stored to deliver your episodes.
We do not sell your personal data. Payment processing is handled by Stripe and PayPal.
</p>
<p className="mt-4 text-sm text-muted-foreground">
This is placeholder copy replace with your reviewed privacy policy before launch.
</p>
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
import type { Metadata } from "next";
export const metadata: Metadata = { title: "Terms of Service" };
export default function TermsPage() {
return (
<div className="container max-w-3xl py-20 md:py-24">
<h1 className="font-display text-4xl font-extrabold tracking-tight md:text-5xl">Terms of Service</h1>
<p className="mt-4 text-muted-foreground">
These terms govern your use of PodcastYes. By creating an account you agree to use the
service lawfully and to retain responsibility for the content you generate. AI-generated
scripts, audio, and artwork are provided as-is; review them before publishing. Subscriptions
renew automatically until cancelled, and usage limits reset monthly.
</p>
<p className="mt-4 text-sm text-muted-foreground">
This is placeholder copy replace with your reviewed legal terms before launch.
</p>
</div>
);
}