Turnstile bot protection (sign-in, sign-up, password-reset): - Register Better Auth's captcha plugin with the cloudflare-turnstile provider; endpoints listed explicitly rather than relying on defaults. /reset-password is intentionally excluded — it is reached only via a single-use emailed token. - Add an explicit-render Turnstile widget component. Tokens are single-use, so each form resets the challenge after a failed submit; submit stays disabled until a token is held. - Read the site key server-side and pass it down as a prop, so rotating it does not require a rebuild. - Fail fast in production when TURNSTILE_SECRET_KEY is missing, and when a secret is set without a site key (that combination would demand a token no form can produce, locking every user out). - Pass a throwaway secret during `next build` in the Dockerfile, mirroring the existing BETTER_AUTH_SECRET treatment, so image builds don't need it. CSP fixes in middleware (these blocked Turnstile entirely): - Add frame-src for challenges.cloudflare.com. Without it the widget's iframe fell back to default-src 'self' and was blocked outright. - Allow 'unsafe-eval' and websockets in DEVELOPMENT only. `next dev` compiles with eval(), so the strict policy threw EvalError and killed hydration — no client JS ran at all, which also meant form submit handlers never fired. Production policy is unchanged and still strict. Also included (concurrent work in the tree): - Admin organizations pages and lib/admin/orgs. - Episode moderation migration, SEO metadata (sitemap, robots, JSON-LD, OG/Twitter images, manifest), Umami analytics, not-found page. Local dev database: docker-compose.dev.yml provisions Postgres 18 on port 5443 (5432-5442 are in use by other local projects). Note: `npx tsc --noEmit` currently fails in app/(app)/team/page.tsx — an `invitations` prop the component does not accept. This predates the commit and will fail `next build` until fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
218 lines
9.1 KiB
TypeScript
218 lines
9.1 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import {
|
|
Sparkles,
|
|
Wand2,
|
|
ShieldCheck,
|
|
Globe,
|
|
Zap,
|
|
HeartHandshake,
|
|
ArrowRight,
|
|
} from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { JsonLd } from "@/components/seo/json-ld";
|
|
import { breadcrumbSchema, graph, webPageSchema } from "@/lib/schema";
|
|
import { pageMetadata } from "@/lib/seo";
|
|
|
|
/** Shared by the page metadata and this page's structured data. */
|
|
const DESCRIPTION =
|
|
"Podcast Distribution AI is an AI studio that turns a single idea into a finished, publishable podcast — script, voices, and cover art — in minutes. Learn why we built it and what we believe.";
|
|
|
|
export const metadata: Metadata = pageMetadata({
|
|
title: "About",
|
|
description: DESCRIPTION,
|
|
path: "/about",
|
|
});
|
|
|
|
const STATS = [
|
|
{ value: "3", label: "AI models in one workflow" },
|
|
{ value: "14+", label: "Realistic narrator voices" },
|
|
{ value: "13+", label: "Languages supported" },
|
|
{ value: "Minutes", label: "From idea to finished episode" },
|
|
];
|
|
|
|
const VALUES = [
|
|
{
|
|
icon: Sparkles,
|
|
title: "Creativity for everyone",
|
|
body: "Powerful production shouldn't require gear, a budget, or technical skill. If you can describe an idea, you can turn it into a finished episode.",
|
|
},
|
|
{
|
|
icon: Wand2,
|
|
title: "Quality you'd actually publish",
|
|
body: "AI drafts are a starting point, not the finish line. Every script and voice is editable, so the final cut always sounds like you.",
|
|
},
|
|
{
|
|
icon: ShieldCheck,
|
|
title: "Built responsibly",
|
|
body: "We screen content against a clear policy, never use your private work to train our models, and make sure you own everything you create.",
|
|
},
|
|
{
|
|
icon: Globe,
|
|
title: "Global from day one",
|
|
body: "Stories aren't only told in English. With 13+ languages, you can produce for an audience anywhere — no re-recording required.",
|
|
},
|
|
{
|
|
icon: Zap,
|
|
title: "Fast, but never careless",
|
|
body: "Minutes from idea to episode, with the controls to get the details right before you hit publish. Speed and craft aren't a trade-off.",
|
|
},
|
|
{
|
|
icon: HeartHandshake,
|
|
title: "Honest and transparent",
|
|
body: "Simple plans, clear limits, cancel anytime. No dark patterns and no surprises — just a tool that respects your time and your work.",
|
|
},
|
|
];
|
|
|
|
export default function AboutPage() {
|
|
return (
|
|
<>
|
|
<JsonLd
|
|
data={graph(
|
|
{
|
|
...webPageSchema({
|
|
path: "/about",
|
|
name: "About",
|
|
description: DESCRIPTION,
|
|
}),
|
|
"@type": "AboutPage",
|
|
},
|
|
breadcrumbSchema([{ name: "About", path: "/about" }])
|
|
)}
|
|
/>
|
|
|
|
{/* Hero */}
|
|
<section className="bg-hero-wash">
|
|
<div className="container max-w-4xl py-24 text-center md:py-32">
|
|
<p className="text-[13px] font-semibold uppercase tracking-[0.04em] text-brand">About us</p>
|
|
<h1 className="mt-4 text-balance font-display text-5xl font-extrabold leading-[1.05] tracking-tight md:text-6xl">
|
|
Making great podcasts possible for everyone
|
|
</h1>
|
|
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground sm:text-xl">
|
|
Podcast Distribution AI is an AI studio that turns a single idea into a finished, publishable episode —
|
|
script, voices, and cover art — in minutes. No microphone, no editing suite, no production
|
|
budget.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Stats */}
|
|
<section className="border-y border-border bg-card">
|
|
<div className="container grid grid-cols-2 gap-8 py-14 lg:grid-cols-4">
|
|
{STATS.map((s) => (
|
|
<div key={s.label} className="text-center">
|
|
<p className="font-display text-4xl font-extrabold tracking-tight text-brand md:text-5xl">
|
|
{s.value}
|
|
</p>
|
|
<p className="mt-2 text-sm text-muted-foreground">{s.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Mission */}
|
|
<section className="py-24 md:py-28">
|
|
<div className="container max-w-3xl text-center">
|
|
<p className="text-[13px] font-semibold uppercase tracking-[0.04em] text-brand">Our mission</p>
|
|
<p className="mt-5 text-balance font-display text-3xl font-bold leading-tight tracking-tight md:text-4xl">
|
|
To remove every barrier between a great idea and a published podcast — so anyone with
|
|
something to say can be{" "}
|
|
<span className="text-brand">heard</span>.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Story */}
|
|
<section className="border-t border-border bg-secondary py-24 md:py-28">
|
|
<div className="container max-w-3xl">
|
|
<p className="text-[13px] font-semibold uppercase tracking-[0.04em] text-brand">Our story</p>
|
|
<h2 className="mt-3 font-display text-3xl font-extrabold tracking-tight md:text-4xl">
|
|
Why we built Podcast Distribution AI
|
|
</h2>
|
|
<div className="mt-8 space-y-5 text-lg leading-relaxed text-muted-foreground">
|
|
<p>
|
|
Podcasting is one of the most personal ways to reach an audience — and one of the
|
|
hardest to start. Scripting, recording, editing, designing artwork, and publishing can
|
|
take days and a stack of tools and skills most people simply don't have. Brilliant
|
|
ideas die in that gap between “I should make a podcast” and actually
|
|
shipping one.
|
|
</p>
|
|
<p>
|
|
We thought the modern AI stack could collapse all of that into a single workflow. GPT-4
|
|
can write a tight, on-brand script. ElevenLabs can voice it with realistic, multi-speaker
|
|
narration. DALL·E can design cover art that matches the topic. Stitched together, they
|
|
turn “I have a topic” into “I have an episode.”
|
|
</p>
|
|
<p>
|
|
So we built Podcast Distribution AI: a studio that lives in your browser. Describe an idea, fine-tune
|
|
the result in a real editor, download the MP3 — then repurpose it into a blog post, a
|
|
social thread, or a newsletter. We're a small team of engineers, audio nerds, and
|
|
storytellers obsessed with making production effortless without making it feel generic.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Values */}
|
|
<section className="py-24 md:py-28">
|
|
<div className="container">
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<p className="text-[13px] font-semibold uppercase tracking-[0.04em] text-brand">
|
|
What we believe
|
|
</p>
|
|
<h2 className="mt-3 font-display text-3xl font-extrabold tracking-tight md:text-4xl">
|
|
The principles behind the product
|
|
</h2>
|
|
</div>
|
|
<div className="mt-16 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
{VALUES.map((v) => (
|
|
<div
|
|
key={v.title}
|
|
className="rounded-2xl border border-border bg-card p-7 transition-all hover:-translate-y-0.5 hover:shadow-md"
|
|
>
|
|
<span className="flex h-12 w-12 items-center justify-center rounded-2xl bg-brand/10 text-brand">
|
|
<v.icon className="h-6 w-6" />
|
|
</span>
|
|
<h3 className="mt-5 font-display text-lg font-bold tracking-tight">{v.title}</h3>
|
|
<p className="mt-2 leading-relaxed text-muted-foreground">{v.body}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* CTA */}
|
|
<section className="pb-24 md:pb-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">
|
|
Come make something worth listening to
|
|
</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>
|
|
<div className="mt-9 flex flex-col justify-center gap-3 sm:flex-row">
|
|
<Button asChild size="lg" variant="brand">
|
|
<Link href="/sign-up">
|
|
Start free <ArrowRight className="h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
<Button
|
|
asChild
|
|
size="lg"
|
|
variant="outline"
|
|
className="border-white/25 bg-transparent text-primary-foreground hover:bg-white/10 hover:text-primary-foreground"
|
|
>
|
|
<Link href="/pricing">See pricing</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|