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
+77
View File
@@ -0,0 +1,77 @@
import {readdirSync, readFileSync} from "node:fs";
import {join} from "node:path";
import matter from "gray-matter";
import {marked} from "marked";
// Posts are authored as markdown under src/posts. This module uses the Node fs
// API and must only be imported from server components / server code — never
// from a "use client" file, or the markdown libraries leak into the bundle.
export type BlogPost = {
slug: string;
title: string;
author: string;
date: string;
tags: string[];
excerpt: string;
readingMinutes: number;
html: string;
};
export type BlogPostSummary = Omit<BlogPost, "html">;
const POSTS_DIR = join(process.cwd(), "src/posts");
function slugFromFile(file: string): string {
return file.replace(/\.md$/, "");
}
function parsePost(file: string, raw: string): BlogPost {
const {data, content} = matter(raw);
const words = content.trim().split(/\s+/).filter(Boolean).length;
return {
slug: slugFromFile(file),
title: String(data.title ?? "Untitled"),
author: String(data.author ?? "Jamie Pine"),
date:
data.date instanceof Date
? data.date.toISOString().slice(0, 10)
: String(data.date ?? ""),
tags: Array.isArray(data.tags) ? data.tags.map(String) : [],
excerpt: String(data.excerpt ?? ""),
readingMinutes: Math.max(1, Math.ceil(words / 220)),
html: marked.parse(content, {async: false}) as string,
};
}
export function loadAllPosts(): BlogPost[] {
let files: string[] = [];
try {
files = readdirSync(POSTS_DIR).filter((f) => f.endsWith(".md"));
} catch {
return []; // posts dir doesn't exist yet — treat as empty
}
return files
.map((file) => parsePost(file, readFileSync(join(POSTS_DIR, file), "utf8")))
.sort((a, b) => (a.date < b.date ? 1 : -1));
}
export function listPosts(): BlogPostSummary[] {
return loadAllPosts().map(({html: _html, ...summary}) => summary);
}
export function getPost(slug: string): BlogPost | null {
return loadAllPosts().find((post) => post.slug === slug) ?? null;
}
export function formatDate(date: string): string {
if (!date) return "";
const d = new Date(`${date}T00:00:00Z`);
if (Number.isNaN(d.getTime())) return date;
return d.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
timeZone: "UTC",
});
}
+42
View File
@@ -5,11 +5,53 @@ export const LATEST_VERSION = 'v0.1.0';
export const GITHUB_REPO = 'https://github.com/jamiepine/voicebox';
export const GITHUB_RELEASES_PAGE = `${GITHUB_REPO}/releases`;
export const DONATE_URL = 'https://buymeacoffee.com/jamiepine';
export const SPONSOR_CHECKOUT_URL = 'https://buy.stripe.com/eVqdRad3n16ubcqf201Jm00';
export const SPONSOR_CONTACT_EMAIL = '[email protected]';
// $VOICEBOX — the official community token on Solana
export const TOKEN_TICKER = '$VOICEBOX';
export const TOKEN_CONTRACT_ADDRESS = 'FpzZHtp5tbvz6xndEtoJHoGEWcT7cFEuscdCh9RApump';
export const TOKEN_PUMP_URL = `https://pump.fun/coin/${TOKEN_CONTRACT_ADDRESS}`;
// Solscan token page — lets anyone inspect supply, holders, and history.
export const TOKEN_SOLSCAN_URL = `https://solscan.io/token/${TOKEN_CONTRACT_ADDRESS}`;
export const TOKEN_TOTAL_SUPPLY = '1B';
// On-chain transparency log — locks and buyback+burns.
// Add a new entry every time a lock or burn happens; set `txUrl` to its Solscan
// link to make the card a live, verifiable proof. Entries without a txUrl render
// as "proof link pending" — fill them in as soon as the hash is available.
export interface TokenProof {
kind: 'lock' | 'burn';
label: string;
detail: string;
/** Solscan (or locker) URL proving the action. Empty = pending, shown as such. */
txUrl: string;
}
export const TOKEN_PROOFS: TokenProof[] = [
{
kind: 'lock',
label: 'Launch liquidity lock',
detail:
'Liquidity and a portion of dev holdings were locked at launch (~6.6% top holder), so the supply can be verified on-chain from day one.',
txUrl: '', // TODO(jamie): add the Solscan/locker link for the launch lock
},
{
kind: 'burn',
label: 'Buyback & burn',
detail:
'Bought $VOICEBOX back from the open market and burned it to a dead address, permanently removing it from supply.',
txUrl:
'https://solscan.io/tx/5MjK4CYMBKAewLcjdD6QkM8ctkeG2bjyQhpjNgEumkDbDtoKCVmzKcWwLWsd4QJov8hs5zbGLt3g5vVCp4CBmze5',
},
{
kind: 'burn',
label: 'Buyback & burn',
detail:
'A second buyback and burn — part of an ongoing commitment to keep buying back and reducing supply over time.',
txUrl: '', // TODO(jamie): add the Solscan link for the second burn
},
];
export const DOWNLOAD_LINKS = {
macArm: GITHUB_RELEASES_PAGE,
+126
View File
@@ -0,0 +1,126 @@
// Pricing + cloud data — single source of truth for /pricing and /cloud.
//
// DRAFT: the cloud service is not live yet and the pricing model is not final.
// $12/yr is a launch/intro figure; tier limits below are placeholders to shape
// the page — tune them once the model is decided. Keeping it all here means the
// pages update by editing this file only.
export const CLOUD_STATUS = "coming-soon" as const; // → flips to "live" at launch
export const CLOUD_PRICE_YEARLY = 12; // launch price for the Cloud tier (USD/yr)
/** Where "get notified" CTAs point until there's a real signup flow. */
export const CLOUD_NOTIFY_URL = "https://x.com/VoiceboxAI";
export type BillingPeriod = "monthly" | "annual";
export interface PricingTier {
id: string;
name: string;
tagline: string;
/** USD per month. 0 = free. */
monthly: number;
/** USD per year. 0 = free. Set below 12×monthly to reward annual. */
annual: number;
priceNote?: string;
/** Visually emphasize this tier (the headline plan). */
highlighted?: boolean;
/** Status pill, e.g. "Available now" / "Coming soon". */
badge?: string;
cta: {label: string; href: string};
features: string[];
}
export const PRICING_TIERS: PricingTier[] = [
{
id: "local",
name: "Local",
tagline: "The full app, free forever.",
monthly: 0,
annual: 0,
priceNote: "No account required",
badge: "Available now",
cta: {label: "Download Voicebox", href: "/download"},
features: [
"Voice cloning across every TTS engine",
"Dictation & Capture (audio kept alongside transcript)",
"MCP / agent integration & personalities",
"Unlimited local generations & captures",
"100% open source, runs entirely on your machine",
],
},
{
id: "cloud",
name: "Cloud",
tagline: "Backup & sync for everything you make.",
monthly: 2, // placeholder
annual: CLOUD_PRICE_YEARLY, // $12 launch price (≈50% off monthly)
priceNote: "Launch price · free for $VOICEBOX holders",
highlighted: true,
badge: "Coming soon",
cta: {label: "Get notified", href: CLOUD_NOTIFY_URL},
features: [
"Everything in Local",
"End-to-end encrypted backup — we can't read it",
"Sync across desktop & mobile",
"25 GB encrypted storage", // placeholder
"Up to 5 devices", // placeholder
"30-day version history", // placeholder
],
},
{
id: "studio",
name: "Studio",
tagline: "For power users and professionals.",
monthly: 6, // placeholder
annual: 48, // placeholder
priceNote: "Placeholder — pricing TBD",
badge: "Coming soon",
cta: {label: "Get notified", href: CLOUD_NOTIFY_URL},
features: [
"Everything in Cloud",
"250 GB encrypted storage", // placeholder
"Unlimited devices", // placeholder
"1-year version history", // placeholder
"Priority support",
],
},
];
/** Annual savings vs paying 12× the monthly price, as a whole percent (0 if none). */
export function annualSavingsPercent(tier: PricingTier): number {
if (tier.monthly <= 0 || tier.annual <= 0) return 0;
const full = tier.monthly * 12;
return Math.max(0, Math.round((1 - tier.annual / full) * 100));
}
export interface CloudFeature {
title: string;
body: string;
}
export const CLOUD_FEATURES: CloudFeature[] = [
{
title: "End-to-end encrypted",
body: "Everything is encrypted on your device before it leaves it. The server only ever stores opaque blobs — it literally cannot read your voices, generations, or captures.",
},
{
title: "Back up everything",
body: "Voice profiles, generations, and captures — including the original audio Voicebox keeps alongside each transcript — safe off your machine.",
},
{
title: "Sync across devices",
body: "Pick up on desktop or mobile. Your library follows you, encrypted in transit and at rest, with a per-device key.",
},
{
title: "Generate on the go",
body: "The mobile app pairs with your library so you can dictate and generate anywhere, then find it all waiting back at your desk.",
},
{
title: "You hold the keys",
body: "A recovery phrase you control is the root of your encryption. Lose your devices and you can still restore — but no one else, including us, ever can.",
},
{
title: "Optional & local-first",
body: "Voicebox works fully offline without an account. Cloud is an add-on for when you want backup and sync — never a requirement.",
},
];