mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-20 15:20:39 -07:00
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:
@@ -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",
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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.",
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user