Add $VOICEBOX token page, blog, and cloud/pricing pages

- /token: dedicated page with on-chain transparency (liquidity lock +
  buyback/burns), holder utility, official-token clarity, and FAQ. The
  landing now shows a teaser linking to it; navbar + footer repointed
  from pump.fun to /token.
- /blog: file-based markdown blog (gray-matter + marked) with per-post
  Open Graph images; first post "Why Voicebox has a token".
- /cloud: end-to-end encrypted backup & sync product page.
- /pricing: Local / Cloud / Studio tiers with a monthly/annual toggle;
  $VOICEBOX holders get Cloud free.
- Add Testimonials section to the landing.
- Navbar: remove API/Download, add Pricing/Blog, fix center-nav spacing.
- docker-compose: host port 17493->17600 for local dev coexistence.
This commit is contained in:
James Pine
2026-06-27 17:34:53 -07:00
parent c6a59f4477
commit 7d9a384ee4
20 changed files with 1675 additions and 25 deletions
@@ -0,0 +1,104 @@
import {readFileSync} from "node:fs";
import {join} from "node:path";
import {ImageResponse} from "next/og";
import {formatDate, getPost, loadAllPosts} from "@/lib/blog";
// Per-post Open Graph image, generated with Satori at build time (static export
// of each post route) and served as PNG. Note: this runs in the Satori renderer,
// which only understands inline styles + flexbox and a subset of CSS — no
// Tailwind classes, no `filter: blur()`. Glows are done with radial gradients.
export const size = {width: 1200, height: 630};
export const contentType = "image/png";
export const alt = "Voicebox Blog";
// Pre-build an image for every post route (mirrors the page's static params).
export function generateStaticParams() {
return loadAllPosts().map((post) => ({slug: post.slug}));
}
// 8-bit PNG decodes reliably in Satori; the 1024px logos are 16-bit and don't.
const logo = `data:image/png;base64,${readFileSync(
join(process.cwd(), "public/apple-touch-icon.png"),
).toString("base64")}`;
function titleFontSize(title: string): number {
if (title.length <= 38) return 76;
if (title.length <= 64) return 60;
return 48;
}
export default async function OgImage({
params,
}: {
params: Promise<{slug: string}>;
}) {
const {slug} = await params;
const post = getPost(slug);
const title = post?.title ?? "Voicebox Blog";
const meta = post
? `${post.author} · ${formatDate(post.date)}`
: "Open source voice cloning. Local-first.";
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: 80,
background:
"radial-gradient(ellipse 80% 70% at 30% 30%, hsla(43,60%,50%,0.14) 0%, hsla(43,60%,50%,0.04) 40%, transparent 70%), linear-gradient(180deg, hsl(30,4%,6%) 0%, hsl(30,4%,4%) 100%)",
}}
>
{/* Top: logo + eyebrow */}
<div style={{display: "flex", alignItems: "center", gap: 24}}>
{/* biome-ignore lint/performance/noImgElement: Satori only renders <img> */}
<img src={logo} width={88} height={88} alt="" />
<div
style={{
display: "flex",
fontSize: 26,
letterSpacing: 6,
fontWeight: 600,
textTransform: "uppercase",
color: "hsl(43, 60%, 58%)",
}}
>
Voicebox Blog
</div>
</div>
{/* Title */}
<div
style={{
display: "flex",
fontSize: titleFontSize(title),
lineHeight: 1.1,
fontWeight: 700,
letterSpacing: -1,
color: "hsl(30, 10%, 94%)",
maxWidth: 1000,
}}
>
{title}
</div>
{/* Footer meta */}
<div
style={{
display: "flex",
fontSize: 28,
color: "hsl(30, 5%, 55%)",
}}
>
{meta}
</div>
</div>
),
size,
);
}
+92
View File
@@ -0,0 +1,92 @@
import type {Metadata} from "next";
import Link from "next/link";
import {notFound} from "next/navigation";
import {Footer} from "@/components/Footer";
import {Navbar} from "@/components/Navbar";
import {formatDate, getPost, loadAllPosts} from "@/lib/blog";
export function generateStaticParams() {
return loadAllPosts().map((post) => ({slug: post.slug}));
}
export async function generateMetadata({
params,
}: {
params: Promise<{slug: string}>;
}): Promise<Metadata> {
const {slug} = await params;
const post = getPost(slug);
if (!post) return {title: "Post not found — Voicebox"};
return {
title: `${post.title} — Voicebox`,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: "article",
url: `https://voicebox.sh/blog/${post.slug}`,
// og:image / twitter:image come from the colocated opengraph-image.tsx
},
twitter: {
card: "summary_large_image",
title: post.title,
description: post.excerpt,
},
};
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{slug: string}>;
}) {
const {slug} = await params;
const post = getPost(slug);
if (!post) notFound();
return (
<>
<Navbar />
<main className="mx-auto w-full max-w-3xl px-6 pt-32 pb-20">
<Link
href="/blog"
className="font-mono text-sm text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline"
>
Back to blog
</Link>
<header className="mt-8 border-b border-border pb-10">
{post.tags.length > 0 ? (
<div className="mb-5 flex flex-wrap gap-2">
{post.tags.map((tag) => (
<span
key={tag}
className="rounded-full border border-border/60 bg-card/40 px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
>
{tag}
</span>
))}
</div>
) : null}
<h1 className="text-4xl md:text-5xl font-bold tracking-tighter text-foreground">
{post.title}
</h1>
<p className="mt-5 font-mono text-sm text-muted-foreground">
by <span className="text-foreground">{post.author}</span> ·{" "}
{formatDate(post.date)} · {post.readingMinutes} min read
</p>
</header>
<article
className="blog-prose mt-10"
// Content is authored markdown from this repo, not user input.
// biome-ignore lint/security/noDangerouslySetInnerHtml: trusted local markdown
dangerouslySetInnerHTML={{__html: post.html}}
/>
</main>
<Footer />
</>
);
}
+86
View File
@@ -0,0 +1,86 @@
import type {Metadata} from "next";
import Link from "next/link";
import {Footer} from "@/components/Footer";
import {Navbar} from "@/components/Navbar";
import {formatDate, listPosts} from "@/lib/blog";
export const metadata: Metadata = {
title: "Blog — Voicebox",
description: "Notes from building Voicebox — the open-source AI voice studio.",
openGraph: {
title: "Voicebox Blog",
description: "Notes from building Voicebox — the open-source AI voice studio.",
type: "website",
url: "https://voicebox.sh/blog",
images: [{url: "/og.webp", width: 1200, height: 630}],
},
};
export default function BlogIndexPage() {
const posts = listPosts();
return (
<>
<Navbar />
<main className="mx-auto w-full max-w-3xl px-6 pt-32 pb-20">
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent mb-4">
Blog
</div>
<h1 className="text-4xl md:text-5xl font-bold tracking-tighter text-foreground">
Notes from building Voicebox.
</h1>
<p className="mt-5 max-w-2xl text-lg text-muted-foreground">
The story behind the project, what's shipping next, and the occasional
look under the hood.
</p>
{posts.length === 0 ? (
<p className="mt-16 border-t border-border pt-10 text-muted-foreground">
Nothing published yet.
</p>
) : (
<ul className="mt-16 border-t border-border">
{posts.map((post) => (
<li key={post.slug}>
<Link
href={`/blog/${post.slug}`}
className="group grid gap-4 border-b border-border py-10 md:grid-cols-[11rem_1fr] md:gap-10"
>
<div className="font-mono text-sm text-muted-foreground md:pt-1.5">
<p>{formatDate(post.date)}</p>
<p className="mt-1">{post.readingMinutes} min read</p>
</div>
<div className="max-w-2xl">
<h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground transition-colors group-hover:text-accent">
{post.title}
</h2>
{post.excerpt ? (
<p className="mt-3 leading-7 text-muted-foreground">
{post.excerpt}
</p>
) : null}
{post.tags.length > 0 ? (
<div className="mt-5 flex flex-wrap gap-2">
{post.tags.map((tag) => (
<span
key={tag}
className="rounded-full border border-border/60 bg-card/40 px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
>
{tag}
</span>
))}
</div>
) : null}
</div>
</Link>
</li>
))}
</ul>
)}
</main>
<Footer />
</>
);
}
+188
View File
@@ -0,0 +1,188 @@
import {ArrowRight, Cloud, KeyRound, Lock, ShieldCheck} from "lucide-react";
import type {Metadata} from "next";
import Link from "next/link";
import {Footer} from "@/components/Footer";
import {Navbar} from "@/components/Navbar";
import {CLOUD_FEATURES, CLOUD_NOTIFY_URL} from "@/lib/pricing";
export const metadata: Metadata = {
title: "Cloud Backup & Sync — Voicebox",
description:
"End-to-end encrypted backup and sync for your Voicebox library. We can't read your data — only your devices can. Optional, local-first, free for $VOICEBOX holders.",
openGraph: {
title: "Voicebox Cloud — encrypted backup & sync",
description:
"End-to-end encrypted backup and sync across desktop and mobile. The server is blind — only your devices can decrypt.",
type: "website",
url: "https://voicebox.sh/cloud",
images: [{url: "/og.webp", width: 1200, height: 630}],
},
};
const STEPS = [
{
icon: Lock,
title: "Encrypted on your device",
body: "Profiles, generations, and captures are encrypted locally with keys only you hold — before anything is uploaded.",
},
{
icon: Cloud,
title: "Stored as opaque blobs",
body: "The server keeps your encrypted objects and a sync feed. It can route and store them, but never decrypt them.",
},
{
icon: KeyRound,
title: "Only your devices decrypt",
body: "Each device unwraps your master key on pairing. A recovery phrase you control lets you restore everything to a new one.",
},
];
export default function CloudPage() {
return (
<>
<Navbar />
{/* ── Hero ─────────────────────────────────────────────────── */}
<section className="relative pt-32 pb-16">
<div className="hero-glow hero-glow-fade pointer-events-none absolute inset-0 -top-32">
<div className="absolute left-1/2 top-0 -translate-x-1/2 w-[900px] h-[500px] rounded-full bg-accent/12 blur-[140px]" />
</div>
<div className="relative mx-auto max-w-4xl px-6 text-center">
<div className="fade-in mb-6 inline-flex items-center gap-2 rounded-full border border-border/60 bg-card/40 px-3 py-1">
<Cloud className="h-3.5 w-3.5 text-accent" />
<span className="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
Voicebox Cloud · coming soon
</span>
</div>
<h1 className="fade-in text-5xl font-bold tracking-tighter leading-[0.95] text-foreground md:text-6xl lg:text-7xl">
Your studio, backed up and in sync.
</h1>
<p className="fade-in mx-auto mt-6 max-w-2xl text-lg text-muted-foreground md:text-xl">
Optional, end-to-end encrypted backup and sync for your entire
Voicebox library. We can't read a byte of it — only your devices
can. Free for{" "}
<Link href="/token" className="text-foreground underline-offset-4 hover:underline">
$VOICEBOX
</Link>{" "}
holders.
</p>
<div className="fade-in mt-10 flex flex-row items-center justify-center gap-3 sm:gap-4">
<Link
href="/pricing"
className="rounded-full bg-accent px-8 py-3.5 text-sm font-semibold uppercase tracking-wider text-white shadow-[0_4px_20px_hsl(43_60%_50%/0.3),inset_0_2px_0_rgba(255,255,255,0.2),inset_0_-2px_0_rgba(0,0,0,0.1)] transition-all hover:bg-accent-faint"
>
See pricing
</Link>
<a
href={CLOUD_NOTIFY_URL}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 rounded-full border border-border/60 bg-card/40 backdrop-blur-sm px-6 py-3 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground hover:border-border"
>
Get notified
<ArrowRight className="h-4 w-4" />
</a>
</div>
</div>
</section>
{/* ── Features ─────────────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-5xl px-6">
<div className="grid gap-4 md:grid-cols-3">
{CLOUD_FEATURES.map((f) => (
<div
key={f.title}
className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6"
>
<h3 className="text-[15px] font-semibold text-foreground mb-2">
{f.title}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
{f.body}
</p>
</div>
))}
</div>
</div>
</section>
{/* ── How it works ─────────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-4xl px-6">
<div className="text-center mb-12">
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent mb-4">
How it works
</div>
<h2 className="text-3xl md:text-4xl font-semibold tracking-tight text-foreground">
Zero-knowledge by design.
</h2>
</div>
<div className="grid gap-4 md:grid-cols-3">
{STEPS.map((step, i) => {
const Icon = step.icon;
return (
<div
key={step.title}
className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6"
>
<div className="flex items-center gap-3 mb-3">
<Icon className="h-5 w-5 text-accent" />
<span className="font-mono text-xs text-muted-foreground/60">
0{i + 1}
</span>
</div>
<h3 className="text-[15px] font-semibold text-foreground mb-2">
{step.title}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
{step.body}
</p>
</div>
);
})}
</div>
</div>
</section>
{/* ── Trust callout ────────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-3xl px-6">
<div className="rounded-2xl border-2 border-accent/40 bg-card/60 backdrop-blur-sm p-8 md:p-10 text-center shadow-[0_8px_40px_hsl(43_60%_50%/0.08)]">
<ShieldCheck className="h-7 w-7 text-accent mx-auto mb-4" />
<h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3">
We can't see your data. That's the point.
</h2>
<p className="text-muted-foreground leading-relaxed max-w-2xl mx-auto">
Voicebox is local-first and privacy-first. The cloud keeps that
promise: your library is encrypted before it leaves your device,
the server stores only ciphertext, and the keys never leave your
control. Same philosophy as the app just backed up.
</p>
<div className="mt-8 flex flex-row items-center justify-center gap-3">
<Link
href="/pricing"
className="rounded-full bg-accent px-6 py-3 text-sm font-semibold text-white shadow-[0_4px_20px_hsl(43_60%_50%/0.3)] transition-all hover:bg-accent-faint"
>
See pricing
</Link>
<Link
href="/token"
className="rounded-full border border-border/60 bg-card/40 px-6 py-3 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground hover:border-border"
>
Free for holders
</Link>
</div>
</div>
</div>
</section>
<Footer />
</>
);
}
+93
View File
@@ -142,6 +142,99 @@
will-change: transform;
} */
/* Blog post typography (rendered markdown via marked) */
.blog-prose {
color: hsl(var(--muted-foreground));
font-size: 1.0625rem;
line-height: 1.75;
}
.blog-prose > * + * {
margin-top: 1.25em;
}
.blog-prose h2 {
margin-top: 2.25em;
margin-bottom: 0.75em;
font-size: 1.6rem;
font-weight: 600;
letter-spacing: -0.02em;
color: hsl(var(--foreground));
}
.blog-prose h3 {
margin-top: 1.75em;
margin-bottom: 0.5em;
font-size: 1.25rem;
font-weight: 600;
color: hsl(var(--foreground));
}
.blog-prose p,
.blog-prose ul,
.blog-prose ol,
.blog-prose blockquote {
color: hsl(var(--muted-foreground));
}
.blog-prose strong {
color: hsl(var(--foreground));
font-weight: 600;
}
.blog-prose a {
color: hsl(var(--foreground));
text-decoration: underline;
text-underline-offset: 3px;
text-decoration-color: hsl(var(--accent) / 0.5);
transition: color 0.15s;
}
.blog-prose a:hover {
color: hsl(var(--accent));
}
.blog-prose ul,
.blog-prose ol {
padding-left: 1.4em;
}
.blog-prose ul {
list-style: disc;
}
.blog-prose ol {
list-style: decimal;
}
.blog-prose li + li {
margin-top: 0.4em;
}
.blog-prose blockquote {
border-left: 2px solid hsl(var(--accent) / 0.5);
padding-left: 1.25em;
font-style: italic;
}
.blog-prose code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.875em;
background: hsl(var(--muted));
color: hsl(var(--foreground));
padding: 0.15em 0.4em;
border-radius: 0.3rem;
}
.blog-prose pre {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.75rem;
padding: 1.1em 1.25em;
overflow-x: auto;
}
.blog-prose pre code {
background: transparent;
padding: 0;
font-size: 0.875rem;
color: hsl(var(--foreground));
}
.blog-prose hr {
border: none;
border-top: 1px solid hsl(var(--border));
margin: 2.5em 0;
}
.blog-prose img {
border-radius: 0.75rem;
border: 1px solid hsl(var(--border));
}
/* Scrollbar hiding */
::-webkit-scrollbar {
display: none;
+8 -4
View File
@@ -12,7 +12,8 @@ import {Navbar} from "@/components/Navbar";
import {Personalities} from "@/components/Personalities";
import {AppleIcon, LinuxIcon, WindowsIcon} from "@/components/PlatformIcons";
import {SupportedModels} from "@/components/SupportedModels";
import {TokenSection} from "@/components/TokenSection";
import {Testimonials} from "@/components/Testimonials";
import {TokenTeaser} from "@/components/TokenTeaser";
import {TutorialsSection} from "@/components/TutorialsSection";
import {VoiceCreator} from "@/components/VoiceCreator";
import {GITHUB_REPO} from "@/lib/constants";
@@ -131,9 +132,6 @@ export default function Home() {
</div>
</section>
{/* ── $VOICEBOX token ──────────────────────────────────────── */}
<TokenSection />
{/* ── Features ─────────────────────────────────────────────── */}
<Features />
@@ -158,6 +156,9 @@ export default function Home() {
{/* ── Supported models ─────────────────────────────────────── */}
<SupportedModels />
{/* ── Testimonials ─────────────────────────────────────────── */}
<Testimonials />
{/* ── Download Section ─────────────────────────────────────── */}
<section id="download" className="border-t border-border py-24">
<div className="mx-auto max-w-4xl px-6">
@@ -241,6 +242,9 @@ export default function Home() {
</div>
</section>
{/* ── $VOICEBOX token (teaser → /token) ─────────────────────── */}
<TokenTeaser />
{/* ── Footer ───────────────────────────────────────────────── */}
<Footer />
</>
+131
View File
@@ -0,0 +1,131 @@
import {Coins} from "lucide-react";
import type {Metadata} from "next";
import Link from "next/link";
import {Footer} from "@/components/Footer";
import {Navbar} from "@/components/Navbar";
import {PricingTiers} from "@/components/PricingTiers";
export const metadata: Metadata = {
title: "Pricing — Voicebox",
description:
"Voicebox is free and open source forever. Optional, end-to-end encrypted cloud backup & sync — free for $VOICEBOX holders.",
openGraph: {
title: "Voicebox Pricing",
description:
"The app is free forever. Cloud backup & sync is an optional add-on — free for $VOICEBOX holders.",
type: "website",
url: "https://voicebox.sh/pricing",
images: [{url: "/og.webp", width: 1200, height: 630}],
},
};
const FAQ = [
{
q: "Is the app really free?",
a: "Yes — Voicebox is free and open source, forever. Cloning, dictation, every TTS engine, MCP, personalities: all of it runs locally with no account. The paid plans only add optional cloud backup & sync.",
},
{
q: "What's encrypted in the cloud?",
a: "Everything. Your profiles, generations, and captures are end-to-end encrypted on your device before upload. The server stores only ciphertext and can never read your data.",
},
{
q: "Do $VOICEBOX holders really get Cloud free?",
a: "Yes. Holding the token unlocks the Cloud tier at no cost. The app itself is free regardless — the token is an optional way to support the project.",
},
{
q: "What counts toward storage?",
a: "Your encrypted objects — generated audio, the original audio kept with each capture, and profile data. Plans differ mainly on storage, device count, and version-history length.",
},
{
q: "Can I cancel anytime?",
a: "Yes. Cloud is a subscription you can cancel whenever you like; your local library always stays on your machine and keeps working.",
},
];
export default function PricingPage() {
return (
<>
<Navbar />
{/* ── Hero ─────────────────────────────────────────────────── */}
<section className="relative pt-32 pb-12">
<div className="hero-glow hero-glow-fade pointer-events-none absolute inset-0 -top-32">
<div className="absolute left-1/2 top-0 -translate-x-1/2 w-[900px] h-[460px] rounded-full bg-accent/12 blur-[140px]" />
</div>
<div className="relative mx-auto max-w-4xl px-6 text-center">
<div className="fade-in mb-4 text-[11px] font-semibold uppercase tracking-[0.22em] text-accent">
Pricing
</div>
<h1 className="fade-in text-5xl font-bold tracking-tighter text-foreground md:text-6xl">
The app is free. Forever.
</h1>
<p className="fade-in mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">
Everything that makes Voicebox great runs locally at no cost. Pay
only if you want optional, encrypted cloud backup & sync and
holders get that free.
</p>
</div>
</section>
{/* ── Tiers (with monthly/annual toggle) ───────────────────── */}
<section className="pb-8">
<PricingTiers />
</section>
{/* ── Holder callout ───────────────────────────────────────── */}
<section className="py-12">
<div className="mx-auto max-w-3xl px-6">
<Link
href="/token"
className="group flex flex-col items-center gap-3 rounded-2xl border border-accent/30 bg-card/40 backdrop-blur-sm px-6 py-8 text-center transition-colors hover:border-accent/50"
>
<Coins className="h-6 w-6 text-accent" />
<h2 className="text-xl md:text-2xl font-semibold tracking-tight text-foreground">
Hold $VOICEBOX, get Cloud free.
</h2>
<p className="max-w-xl text-sm text-muted-foreground">
The token is an optional way to back the project and holders get
the Cloud tier at no cost. Learn how it works and verify everything
on-chain.
</p>
<span className="mt-1 text-sm font-medium text-accent group-hover:underline underline-offset-4">
View the token
</span>
</Link>
</div>
</section>
{/* ── FAQ ──────────────────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-3xl px-6">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-semibold tracking-tight text-foreground">
Questions
</h2>
</div>
<div className="grid gap-4 sm:grid-cols-2">
{FAQ.map((item) => (
<div
key={item.q}
className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6"
>
<h3 className="text-[15px] font-semibold text-foreground mb-2">
{item.q}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
{item.a}
</p>
</div>
))}
</div>
<p className="text-center text-xs text-muted-foreground/70 mt-10 max-w-2xl mx-auto">
Cloud pricing and limits are not final they'll be confirmed at
launch.
</p>
</div>
</section>
<Footer />
</>
);
}
+281
View File
@@ -0,0 +1,281 @@
import {
ArrowUpRight,
Check,
Cloud,
Flame,
Heart,
Lock,
Rocket,
ShieldCheck,
} from "lucide-react";
import type {Metadata} from "next";
import {Footer} from "@/components/Footer";
import {Navbar} from "@/components/Navbar";
import {TokenSection} from "@/components/TokenSection";
import {
TOKEN_PROOFS,
TOKEN_SOLSCAN_URL,
TOKEN_TICKER,
} from "@/lib/constants";
export const metadata: Metadata = {
title: `${TOKEN_TICKER} — The official Voicebox token`,
description: `${TOKEN_TICKER} is the official community token for Voicebox on Solana. Entirely optional — Voicebox is, and always will be, free and open source.`,
openGraph: {
title: `${TOKEN_TICKER} on Solana`,
description: `The official community token for Voicebox. Optional, just for fun — Voicebox stays free and open source.`,
type: "website",
url: "https://voicebox.sh/token",
images: [{url: "/og.webp", width: 1200, height: 630}],
},
};
const USE_OF_FUNDS = [
{
icon: Rocket,
title: "Full-time development",
body: "The token is the equivalent of a salary — it lets me work on Voicebox every day instead of squeezing it around other work.",
},
{
icon: Cloud,
title: "Mobile + cloud backup & sync",
body: "Shipping the mobile app and encrypted cloud backup/sync so your generations and captures are safe and available anywhere.",
},
{
icon: Heart,
title: "More engines, more hardware",
body: "Adding TTS engines and broadening GPU / OS support so Voicebox runs great on whatever you've got.",
},
];
const FAQ = [
{
q: "Do I need the token to use Voicebox?",
a: "No. Voicebox is free and open source, and every feature works without ever touching the token. It exists purely for supporters who want to back the project and have some fun.",
},
{
q: "Is this an investment?",
a: "No. $VOICEBOX is a community token, not a security or a promise of returns. There is no roadmap of financial milestones, and nothing here is financial advice. Only spend what you're comfortable with.",
},
{
q: "How do I buy it?",
a: "Copy the contract address above, then buy on pump.fun with a Solana wallet. Always verify the address matches the one on this page — impersonators are common.",
},
{
q: "Does buying it fund development?",
a: "Yes — going full-time on Voicebox is funded by the token, alongside donations. The surest way to support the project either way is to use it, star the repo, and tell people about it.",
},
];
export default function TokenPage() {
return (
<>
<Navbar />
{/* Top padding clears the fixed navbar; TokenSection carries the
header, contract address, and buy CTA. */}
<main className="pt-16">
<TokenSection />
{/* ── Why a token ──────────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-3xl px-6">
<div className="text-center mb-12">
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent mb-4">
Why a token
</div>
<h2 className="text-3xl md:text-4xl font-semibold tracking-tight text-foreground">
So I can build this full-time.
</h2>
<p className="text-muted-foreground max-w-2xl mx-auto mt-4">
Voicebox grew to over a million downloads with zero marketing
but donations alone never made full-time work sustainable.{" "}
{TOKEN_TICKER} changed that overnight, and it's already
accelerating everything below. The app stays{" "}
<b className="text-foreground">free, open source, and local-first</b>{" "}
— forever.
</p>
</div>
<div className="grid gap-4 sm:grid-cols-3">
{USE_OF_FUNDS.map((item) => {
const Icon = item.icon;
return (
<div
key={item.title}
className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6"
>
<Icon className="h-5 w-5 text-accent mb-3" />
<h3 className="text-[15px] font-semibold text-foreground mb-2">
{item.title}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
{item.body}
</p>
</div>
);
})}
</div>
</div>
</section>
{/* ── Holder utility ───────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-3xl px-6">
<div className="rounded-2xl border-2 border-accent/40 bg-card/60 backdrop-blur-sm p-8 md:p-10 shadow-[0_8px_40px_hsl(43_60%_50%/0.08)]">
<div className="flex items-center gap-2 mb-4">
<Cloud className="h-5 w-5 text-accent" />
<span className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent">
Holder perk · coming soon
</span>
</div>
<h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3">
Cloud backup & sync — free for holders.
</h2>
<p className="text-muted-foreground leading-relaxed">
Encrypted cloud backup and sync (and the mobile cloud) will be a
paid service — roughly{" "}
<b className="text-foreground">$12/year</b> for everyone else, and{" "}
<b className="text-foreground">free for {TOKEN_TICKER} holders</b>.
Generate on the go, keep your captures and generations safe, and
pick up on any device.
</p>
</div>
</div>
</section>
{/* ── On-chain transparency ────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-4xl px-6">
<div className="text-center mb-12">
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent mb-4">
On-chain transparency
</div>
<h2 className="text-3xl md:text-4xl font-semibold tracking-tight text-foreground">
Don't trust verify.
</h2>
<p className="text-muted-foreground max-w-2xl mx-auto mt-4">
Liquidity is locked and supply is reduced through ongoing
buyback &amp; burns. Every action is on-chain and linked here, so
you never have to take my word for it.
</p>
</div>
<div className="grid gap-4 sm:grid-cols-3">
{TOKEN_PROOFS.map((proof, i) => {
const Icon = proof.kind === "lock" ? Lock : Flame;
return (
<div
key={`${proof.label}-${i}`}
className="flex flex-col rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6"
>
<Icon className="h-5 w-5 text-accent mb-3" />
<h3 className="text-[15px] font-semibold text-foreground mb-2">
{proof.label}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground flex-1">
{proof.detail}
</p>
{proof.txUrl ? (
<a
href={proof.txUrl}
target="_blank"
rel="noopener noreferrer"
className="mt-4 inline-flex items-center gap-1.5 text-sm font-medium text-foreground/80 hover:text-foreground transition-colors"
>
View on Solscan
<ArrowUpRight className="h-3.5 w-3.5" />
</a>
) : (
<span className="mt-4 inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground/60">
Proof link pending
</span>
)}
</div>
);
})}
</div>
<div className="mt-6 text-center">
<a
href={TOKEN_SOLSCAN_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Inspect supply &amp; holders on Solscan
<ArrowUpRight className="h-4 w-4" />
</a>
</div>
</div>
</section>
{/* ── Official vs community ────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-3xl px-6">
<div className="rounded-2xl border border-border bg-card/40 backdrop-blur-sm p-8">
<div className="flex items-center gap-2 mb-4">
<ShieldCheck className="h-5 w-5 text-accent" />
<h2 className="text-xl md:text-2xl font-semibold tracking-tight text-foreground">
One official token. Accept no substitutes.
</h2>
</div>
<ul className="space-y-3">
<ProofRow text={`${TOKEN_TICKER} is the only official Voicebox token. The mint address on this page is the single source of truth — always verify it.`} />
<ProofRow text="My other projects (including Spacedrive) will never have an official token. This is the only one I'll ever make." />
<ProofRow text="I deployed it myself so liquidity can be locked and the trajectory controlled — and I no longer claim fees on any other community tokens." />
</ul>
</div>
</div>
</section>
{/* ── Good to know ─────────────────────────────────────────── */}
<section className="border-t border-border py-20">
<div className="mx-auto max-w-3xl px-6">
<div className="text-center mb-12">
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent mb-4">
Good to know
</div>
<h2 className="text-3xl md:text-4xl font-semibold tracking-tight text-foreground">
Optional, just for fun.
</h2>
</div>
<div className="grid gap-4 sm:grid-cols-2">
{FAQ.map((item) => (
<div
key={item.q}
className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6"
>
<h3 className="text-[15px] font-semibold text-foreground mb-2">
{item.q}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
{item.a}
</p>
</div>
))}
</div>
<p className="text-center text-xs text-muted-foreground/70 mt-10 max-w-2xl mx-auto">
{TOKEN_TICKER} is a community token with no affiliation to any
exchange or financial product. Nothing on this page is financial
advice. Verify the contract address before buying.
</p>
</div>
</section>
</main>
<Footer />
</>
);
}
function ProofRow({text}: {text: string}) {
return (
<li className="flex items-start gap-3 text-sm text-foreground/90">
<Check className="h-5 w-5 shrink-0 text-accent mt-px" />
<span>{text}</span>
</li>
);
}