mirror of
https://github.com/zoo-apps/industries.git
synced 2026-11-02 17:38:03 +00:00
1032 lines
36 KiB
TypeScript
1032 lines
36 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useState, useEffect } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { cn } from "@/lib/utils";
|
|
import { Button } from "@hanzo/ui";
|
|
import {
|
|
Check,
|
|
Zap,
|
|
Building2,
|
|
Sparkles,
|
|
ArrowRight,
|
|
Loader2,
|
|
DollarSign,
|
|
Heart,
|
|
Shield,
|
|
} from "lucide-react";
|
|
|
|
/* ─── API endpoints ─── */
|
|
|
|
const MODEL_PRICING_API = "https://api.zoo.network/v1/pricing";
|
|
const SUBSCRIPTIONS_API = "https://api.zoo.network/v1/pricing/subscriptions";
|
|
const POLICY_API = "https://api.zoo.network/v1/pricing/policy";
|
|
|
|
/* ─── Types: model pricing (existing) ─── */
|
|
|
|
interface ModelPricing {
|
|
name: string;
|
|
fullName: string;
|
|
tier: string;
|
|
features: string[];
|
|
pricing: {
|
|
input: number;
|
|
output: number;
|
|
cacheRead: number | null;
|
|
cacheWrite: number | null;
|
|
};
|
|
}
|
|
|
|
interface ThirdPartyModel {
|
|
name: string;
|
|
features: string[];
|
|
contextWindow: number;
|
|
pricing: {
|
|
input: number;
|
|
output: number;
|
|
cacheRead: number | null;
|
|
cacheWrite: number | null;
|
|
};
|
|
}
|
|
|
|
interface ModelPricingData {
|
|
updated: string;
|
|
hanzoModels: ModelPricing[];
|
|
thirdPartyModels: ThirdPartyModel[];
|
|
}
|
|
|
|
/* ─── Types: subscription plans ─── */
|
|
|
|
interface SubscriptionPlan {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
priceMonthly: number | null;
|
|
priceAnnual: number | null;
|
|
category?: string;
|
|
features: string[];
|
|
limits?: Record<string, number>;
|
|
popular?: boolean;
|
|
contactSales?: boolean;
|
|
cta?: string;
|
|
ctaLink?: string;
|
|
payouts?: {
|
|
idleResalePercent: number;
|
|
description: string;
|
|
};
|
|
}
|
|
|
|
interface SubscriptionsResponse {
|
|
plans: SubscriptionPlan[];
|
|
}
|
|
|
|
/* ─── Types: pricing policy ─── */
|
|
|
|
interface PricingPolicy {
|
|
transparentPricing: boolean;
|
|
revenueSharing: {
|
|
idleResale: {
|
|
percent: number;
|
|
description: string;
|
|
eligibility: string;
|
|
payoutMethod: string;
|
|
};
|
|
openSource: {
|
|
percent: number;
|
|
description: string;
|
|
program: string;
|
|
};
|
|
};
|
|
principles: string[];
|
|
}
|
|
|
|
/* ─── Helpers ─── */
|
|
|
|
function extractContext(features: string[]): string {
|
|
const ctx = features.find((f) => f.toLowerCase().includes("context"));
|
|
if (!ctx) return "\u2014";
|
|
const match = ctx.match(/(\d+[kK]?)/);
|
|
return match ? match[1].toUpperCase() : ctx;
|
|
}
|
|
|
|
function formatPrice(price: number): string {
|
|
return `$${price.toFixed(2)}`;
|
|
}
|
|
|
|
const TIER_ORDER = ["pro", "pro max", "ultra", "ultra max"];
|
|
|
|
function tierSort(a: ModelPricing, b: ModelPricing): number {
|
|
return TIER_ORDER.indexOf(a.tier) - TIER_ORDER.indexOf(b.tier);
|
|
}
|
|
|
|
const PLAN_ICONS: Record<string, typeof Zap> = {
|
|
developer: Zap,
|
|
pro: Sparkles,
|
|
team: Building2,
|
|
};
|
|
|
|
function formatPlanPrice(
|
|
plan: SubscriptionPlan,
|
|
billingPeriod: "monthly" | "annual"
|
|
): { amount: string; suffix: string } {
|
|
if (plan.contactSales) return { amount: "Custom", suffix: "" };
|
|
const price =
|
|
billingPeriod === "annual" ? plan.priceAnnual : plan.priceMonthly;
|
|
if (price === null || price === undefined || price === 0)
|
|
return { amount: "$0", suffix: "Free forever" };
|
|
return {
|
|
amount: `$${price}`,
|
|
suffix:
|
|
billingPeriod === "annual" ? "/month (billed annually)" : "/month",
|
|
};
|
|
}
|
|
|
|
/* ─── Skeleton components ─── */
|
|
|
|
function PlanSkeleton() {
|
|
return (
|
|
<div className="rounded-2xl border border-border p-8 animate-pulse">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-10 h-10 rounded-lg bg-foreground/10" />
|
|
<div className="h-6 w-24 bg-foreground/10 rounded" />
|
|
</div>
|
|
<div className="h-10 w-20 bg-foreground/10 rounded mb-4" />
|
|
<div className="h-4 w-48 bg-foreground/10 rounded mb-6" />
|
|
<div className="h-10 w-full bg-foreground/10 rounded mb-6" />
|
|
<div className="space-y-3">
|
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
<div key={i} className="flex items-center gap-3">
|
|
<div className="w-5 h-5 rounded bg-foreground/10 flex-shrink-0" />
|
|
<div className="h-4 w-full bg-foreground/10 rounded" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function EnterpriseSkeleton() {
|
|
return (
|
|
<div className="rounded-2xl border border-border p-8 md:p-12 mb-20 animate-pulse">
|
|
<div className="grid md:grid-cols-2 gap-12">
|
|
<div>
|
|
<div className="h-6 w-32 bg-foreground/10 rounded-full mb-4" />
|
|
<div className="h-8 w-64 bg-foreground/10 rounded mb-4" />
|
|
<div className="h-16 w-full bg-foreground/10 rounded mb-6" />
|
|
<div className="flex gap-4">
|
|
<div className="h-10 w-32 bg-foreground/10 rounded" />
|
|
<div className="h-10 w-32 bg-foreground/10 rounded" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
{Array.from({ length: 8 }).map((_, i) => (
|
|
<div key={i} className="flex items-center gap-2">
|
|
<div className="w-5 h-5 rounded bg-foreground/10 flex-shrink-0" />
|
|
<div className="h-4 w-full bg-foreground/10 rounded" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PolicySkeleton() {
|
|
return (
|
|
<div className="rounded-2xl border border-border p-8 md:p-12 mb-20 animate-pulse">
|
|
<div className="h-8 w-48 bg-foreground/10 rounded mb-8" />
|
|
<div className="grid md:grid-cols-2 gap-8 mb-8">
|
|
<div className="h-32 bg-foreground/10 rounded-xl" />
|
|
<div className="h-32 bg-foreground/10 rounded-xl" />
|
|
</div>
|
|
<div className="flex flex-wrap gap-3">
|
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
<div key={i} className="h-8 w-40 bg-foreground/10 rounded-full" />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ─── Main component ─── */
|
|
|
|
export default function PageClient() {
|
|
const [billingPeriod, setBillingPeriod] = useState<"monthly" | "annual">(
|
|
"monthly"
|
|
);
|
|
|
|
// Model pricing state (existing)
|
|
const [modelPricingData, setModelPricingData] =
|
|
useState<ModelPricingData | null>(null);
|
|
const [modelLoading, setModelLoading] = useState(true);
|
|
const [modelError, setModelError] = useState<string | null>(null);
|
|
|
|
// Subscription plans state
|
|
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
|
const [plansLoading, setPlansLoading] = useState(true);
|
|
const [plansError, setPlansError] = useState<string | null>(null);
|
|
|
|
// Policy state
|
|
const [policy, setPolicy] = useState<PricingPolicy | null>(null);
|
|
const [policyLoading, setPolicyLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
// Fetch model pricing
|
|
fetch(MODEL_PRICING_API)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.json();
|
|
})
|
|
.then((data) => {
|
|
setModelPricingData(data);
|
|
setModelLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
console.error("Failed to fetch model pricing:", err);
|
|
setModelError("Unable to load live pricing. Please try again later.");
|
|
setModelLoading(false);
|
|
});
|
|
|
|
// Fetch subscription plans
|
|
fetch(SUBSCRIPTIONS_API)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.json();
|
|
})
|
|
.then((data: SubscriptionsResponse) => {
|
|
setPlans(data.plans ?? []);
|
|
setPlansLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
console.error("Failed to fetch subscription plans:", err);
|
|
setPlansError("Unable to load plans. Please try again later.");
|
|
setPlansLoading(false);
|
|
});
|
|
|
|
// Fetch pricing policy
|
|
fetch(POLICY_API)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.json();
|
|
})
|
|
.then((data: PricingPolicy) => {
|
|
setPolicy(data);
|
|
setPolicyLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
console.error("Failed to fetch pricing policy:", err);
|
|
setPolicyLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
// Separate regular plans from enterprise
|
|
const regularPlans = plans.filter((p) => !p.contactSales);
|
|
const enterprisePlan = plans.find((p) => p.contactSales);
|
|
|
|
const hanzoModels =
|
|
modelPricingData?.hanzoModels?.slice().sort(tierSort) ?? [];
|
|
const thirdPartyModels = modelPricingData?.thirdPartyModels ?? [];
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"min-h-screen transition-colors duration-300",
|
|
"bg-background text-foreground"
|
|
)}
|
|
>
|
|
<main className="pt-32 pb-16 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-7xl mx-auto">
|
|
{/* Hero */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
className="text-center max-w-3xl mx-auto mb-12"
|
|
>
|
|
<h1 className="text-4xl sm:text-5xl font-bold mb-6">
|
|
Simple, transparent pricing
|
|
</h1>
|
|
<p className={cn("text-lg mb-8", "text-muted-foreground")}>
|
|
Start free, scale as you grow. Pay only for what you use.
|
|
</p>
|
|
|
|
{/* Billing Toggle */}
|
|
<div
|
|
className={cn(
|
|
"inline-flex items-center gap-4 p-1 rounded-full",
|
|
"bg-foreground/5"
|
|
)}
|
|
>
|
|
<button
|
|
onClick={() => setBillingPeriod("monthly")}
|
|
className={cn(
|
|
"px-4 py-2 rounded-full text-sm font-medium transition-colors",
|
|
billingPeriod === "monthly"
|
|
? "bg-primary text-primary-foreground"
|
|
: "text-muted-foreground hover:text-foreground"
|
|
)}
|
|
>
|
|
Monthly
|
|
</button>
|
|
<button
|
|
onClick={() => setBillingPeriod("annual")}
|
|
className={cn(
|
|
"px-4 py-2 rounded-full text-sm font-medium transition-colors",
|
|
billingPeriod === "annual"
|
|
? "bg-primary text-primary-foreground"
|
|
: "text-muted-foreground hover:text-foreground"
|
|
)}
|
|
>
|
|
Annual
|
|
<span
|
|
className={cn(
|
|
"ml-1.5 text-xs",
|
|
billingPeriod === "annual"
|
|
? "text-primary-foreground/50"
|
|
: "text-muted-foreground"
|
|
)}
|
|
>
|
|
Save 20%
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Plans Grid */}
|
|
{plansLoading ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-20">
|
|
<PlanSkeleton />
|
|
<PlanSkeleton />
|
|
<PlanSkeleton />
|
|
</div>
|
|
) : plansError ? (
|
|
<div
|
|
className={cn(
|
|
"text-center py-16 mb-20",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{plansError}
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-20">
|
|
{regularPlans.map((plan, index) => {
|
|
const Icon = PLAN_ICONS[plan.id] ?? Zap;
|
|
const highlighted = !!plan.popular;
|
|
const { amount, suffix } = formatPlanPrice(
|
|
plan,
|
|
billingPeriod
|
|
);
|
|
const ctaText =
|
|
plan.cta ??
|
|
(plan.priceMonthly === 0 ? "Start Free" : "Get Started");
|
|
const ctaLink =
|
|
plan.ctaLink ?? `https://console.zoo.ngo?plan=${plan.id}`;
|
|
|
|
return (
|
|
<motion.div
|
|
key={plan.id}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: index * 0.1 }}
|
|
className={cn(
|
|
"relative rounded-2xl border p-8",
|
|
highlighted
|
|
? "border-primary bg-foreground/5"
|
|
: "border-border bg-background/50"
|
|
)}
|
|
>
|
|
{highlighted && (
|
|
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
|
<div
|
|
className={cn(
|
|
"px-3 py-1 rounded-full text-xs font-semibold",
|
|
"bg-primary text-primary-foreground"
|
|
)}
|
|
>
|
|
Most Popular
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div
|
|
className={cn(
|
|
"w-10 h-10 rounded-lg flex items-center justify-center",
|
|
highlighted ? "bg-primary" : "bg-foreground/10"
|
|
)}
|
|
>
|
|
<Icon
|
|
className={cn(
|
|
"w-5 h-5",
|
|
highlighted ? "text-primary-foreground" : ""
|
|
)}
|
|
/>
|
|
</div>
|
|
<h3 className="text-xl font-semibold">{plan.name}</h3>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<span className="text-4xl font-bold">{amount}</span>
|
|
<span
|
|
className={cn(
|
|
"text-sm ml-1",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{suffix}
|
|
</span>
|
|
</div>
|
|
|
|
<p
|
|
className={cn(
|
|
"text-sm mb-6",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{plan.description}
|
|
</p>
|
|
|
|
<a
|
|
href={ctaLink}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Button
|
|
className={cn(
|
|
"w-full mb-6",
|
|
highlighted
|
|
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
: "bg-foreground/10 text-foreground hover:bg-accent"
|
|
)}
|
|
>
|
|
{ctaText}
|
|
</Button>
|
|
</a>
|
|
|
|
<ul className="space-y-3">
|
|
{plan.features.map((feature) => (
|
|
<li key={feature} className="flex items-start gap-3">
|
|
<Check className="h-5 w-5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
|
<span
|
|
className={cn(
|
|
"text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{feature}
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</motion.div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{/* Enterprise Section */}
|
|
{plansLoading ? (
|
|
<EnterpriseSkeleton />
|
|
) : enterprisePlan ? (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
className={cn(
|
|
"rounded-2xl border p-8 md:p-12 mb-20",
|
|
"border-border bg-gradient-to-br from-white/5 to-transparent"
|
|
)}
|
|
>
|
|
<div className="grid md:grid-cols-2 gap-12">
|
|
<div>
|
|
<div
|
|
className={cn(
|
|
"inline-flex items-center gap-2 px-3 py-1 rounded-full text-sm mb-4",
|
|
"bg-foreground/10"
|
|
)}
|
|
>
|
|
<Building2 className="w-4 h-4" />
|
|
{enterprisePlan.name}
|
|
</div>
|
|
<h2 className="text-3xl font-bold mb-4">
|
|
Custom solutions for your organization
|
|
</h2>
|
|
<p className={cn("mb-6", "text-muted-foreground")}>
|
|
{enterprisePlan.description ||
|
|
"Get dedicated infrastructure, custom model training, and enterprise-grade security. Our team will work with you to build the perfect AI solution."}
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
<Link href="/contact">
|
|
<Button
|
|
className={cn(
|
|
"bg-primary text-primary-foreground hover:bg-primary/90"
|
|
)}
|
|
>
|
|
Contact Sales
|
|
<ArrowRight className="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</Link>
|
|
<a
|
|
href="https://cal.com/hanzo"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Button
|
|
variant="outline"
|
|
className={cn(
|
|
"border-border text-foreground hover:bg-accent"
|
|
)}
|
|
>
|
|
Schedule a Demo
|
|
</Button>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
{enterprisePlan.features.map((feature) => (
|
|
<li key={feature} className="flex items-start gap-2">
|
|
<Check className="h-5 w-5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
|
<span
|
|
className={cn(
|
|
"text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{feature}
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
) : null}
|
|
|
|
{/* Transparent Pricing / Revenue Sharing Section */}
|
|
{policyLoading ? (
|
|
<PolicySkeleton />
|
|
) : policy ? (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
className={cn(
|
|
"rounded-2xl border p-8 md:p-12 mb-20",
|
|
"border-border bg-gradient-to-br from-white/5 to-transparent"
|
|
)}
|
|
>
|
|
<h2 className="text-3xl font-bold mb-2">Transparent Pricing</h2>
|
|
<p className={cn("mb-8", "text-muted-foreground")}>
|
|
No hidden fees, no surprises. We share revenue with you.
|
|
</p>
|
|
|
|
{/* Revenue Sharing Cards */}
|
|
<div className="grid md:grid-cols-2 gap-8 mb-8">
|
|
{/* Idle Resale */}
|
|
<div
|
|
className={cn(
|
|
"rounded-xl border p-6",
|
|
"border-border bg-foreground/5"
|
|
)}
|
|
>
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div
|
|
className={cn(
|
|
"w-10 h-10 rounded-lg flex items-center justify-center",
|
|
"bg-primary"
|
|
)}
|
|
>
|
|
<DollarSign className="w-5 h-5 text-primary-foreground" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold">
|
|
Earn up to {policy.revenueSharing.idleResale.percent}%
|
|
on idle compute & LLM resale
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<p
|
|
className={cn("text-sm mb-3", "text-muted-foreground")}
|
|
>
|
|
When your logged-in account is idle, we resell your
|
|
allocated compute and LLM capacity and share up to{" "}
|
|
{policy.revenueSharing.idleResale.percent}% of revenue
|
|
with you.
|
|
</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
<span
|
|
className={cn(
|
|
"text-xs px-2 py-0.5 rounded-full",
|
|
"bg-foreground/10 text-muted-foreground"
|
|
)}
|
|
>
|
|
{policy.revenueSharing.idleResale.eligibility}
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
"text-xs px-2 py-0.5 rounded-full",
|
|
"bg-foreground/10 text-muted-foreground"
|
|
)}
|
|
>
|
|
{policy.revenueSharing.idleResale.payoutMethod}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Open Source Fund */}
|
|
<div
|
|
className={cn(
|
|
"rounded-xl border p-6",
|
|
"border-border bg-foreground/5"
|
|
)}
|
|
>
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div
|
|
className={cn(
|
|
"w-10 h-10 rounded-lg flex items-center justify-center",
|
|
"bg-primary"
|
|
)}
|
|
>
|
|
<Heart className="w-5 h-5 text-primary-foreground" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold">
|
|
{policy.revenueSharing.openSource.percent}% of
|
|
revenue funds open source
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<p
|
|
className={cn("text-sm mb-3", "text-muted-foreground")}
|
|
>
|
|
{policy.revenueSharing.openSource.description ||
|
|
`Via the ${policy.revenueSharing.openSource.program}, proportional to OSS dependency usage.`}
|
|
</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
<span
|
|
className={cn(
|
|
"text-xs px-2 py-0.5 rounded-full",
|
|
"bg-foreground/10 text-muted-foreground"
|
|
)}
|
|
>
|
|
{policy.revenueSharing.openSource.program}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Principles */}
|
|
{policy.principles.length > 0 && (
|
|
<div className="flex flex-wrap gap-3">
|
|
{policy.principles.map((principle) => (
|
|
<div
|
|
key={principle}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<Shield className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
|
<span
|
|
className={cn(
|
|
"text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{principle}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
) : null}
|
|
|
|
{/* API Pricing -- Zen Models (live from API) */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
className="mb-20"
|
|
>
|
|
<h2 className="text-3xl font-bold mb-2">Zen Model Pricing</h2>
|
|
<p className={cn("mb-8", "text-muted-foreground")}>
|
|
Pay-as-you-go per million tokens. All Zen models via{" "}
|
|
<code className="text-xs">api.zoo.network</code>.
|
|
{modelPricingData?.updated && (
|
|
<span
|
|
className={cn("ml-2 text-xs", "text-foreground/30")}
|
|
>
|
|
Updated{" "}
|
|
{new Date(
|
|
modelPricingData.updated
|
|
).toLocaleDateString()}
|
|
</span>
|
|
)}
|
|
</p>
|
|
|
|
{modelLoading ? (
|
|
<div className="flex items-center justify-center py-16">
|
|
<Loader2 className="w-6 h-6 animate-spin mr-2" />
|
|
<span className={cn("text-muted-foreground")}>
|
|
Loading live pricing...
|
|
</span>
|
|
</div>
|
|
) : modelError ? (
|
|
<div
|
|
className={cn(
|
|
"text-center py-16",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{modelError}
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table
|
|
className={cn(
|
|
"w-full border rounded-lg overflow-hidden",
|
|
"border-border"
|
|
)}
|
|
>
|
|
<thead className={cn("bg-foreground/5")}>
|
|
<tr>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold">
|
|
Model
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold">
|
|
Tier
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold">
|
|
Context
|
|
</th>
|
|
<th className="px-6 py-4 text-right text-sm font-semibold">
|
|
Input / 1M tok
|
|
</th>
|
|
<th className="px-6 py-4 text-right text-sm font-semibold">
|
|
Output / 1M tok
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className={cn("divide-y", "divide-white/10")}>
|
|
{hanzoModels.map((m) => (
|
|
<tr
|
|
key={m.name}
|
|
className={cn(
|
|
"transition-colors",
|
|
"hover:bg-accent"
|
|
)}
|
|
>
|
|
<td className="px-6 py-4">
|
|
<div className="font-medium">{m.name}</div>
|
|
<div
|
|
className={cn(
|
|
"text-xs",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{m.fullName}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span
|
|
className={cn(
|
|
"text-xs px-2 py-0.5 rounded-full capitalize",
|
|
"bg-foreground/10 text-muted-foreground"
|
|
)}
|
|
>
|
|
{m.tier}
|
|
</span>
|
|
</td>
|
|
<td
|
|
className={cn(
|
|
"px-6 py-4 text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{extractContext(m.features)}
|
|
</td>
|
|
<td
|
|
className={cn(
|
|
"px-6 py-4 text-right font-mono text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{formatPrice(m.pricing.input)}
|
|
</td>
|
|
<td
|
|
className={cn(
|
|
"px-6 py-4 text-right font-mono text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{formatPrice(m.pricing.output)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
|
|
{/* Third-Party Models (live from API) */}
|
|
{thirdPartyModels.length > 0 && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
className="mb-20"
|
|
>
|
|
<h2 className="text-3xl font-bold mb-2">
|
|
Third-Party Models
|
|
</h2>
|
|
<p className={cn("mb-8", "text-muted-foreground")}>
|
|
100+ additional models via the Zoo LLM Gateway. Same API,
|
|
same SDK.
|
|
</p>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table
|
|
className={cn(
|
|
"w-full border rounded-lg overflow-hidden",
|
|
"border-border"
|
|
)}
|
|
>
|
|
<thead className={cn("bg-foreground/5")}>
|
|
<tr>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold">
|
|
Model
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-sm font-semibold">
|
|
Context
|
|
</th>
|
|
<th className="px-6 py-4 text-right text-sm font-semibold">
|
|
Input / 1M tok
|
|
</th>
|
|
<th className="px-6 py-4 text-right text-sm font-semibold">
|
|
Output / 1M tok
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className={cn("divide-y", "divide-white/10")}>
|
|
{thirdPartyModels.map((m) => (
|
|
<tr
|
|
key={m.name}
|
|
className={cn(
|
|
"transition-colors",
|
|
"hover:bg-accent"
|
|
)}
|
|
>
|
|
<td className="px-6 py-4 font-medium">{m.name}</td>
|
|
<td
|
|
className={cn(
|
|
"px-6 py-4 text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{m.contextWindow
|
|
? `${Math.round(m.contextWindow / 1000)}K`
|
|
: "\u2014"}
|
|
</td>
|
|
<td
|
|
className={cn(
|
|
"px-6 py-4 text-right font-mono text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{formatPrice(m.pricing.input)}
|
|
</td>
|
|
<td
|
|
className={cn(
|
|
"px-6 py-4 text-right font-mono text-sm",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
{formatPrice(m.pricing.output)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p
|
|
className={cn("text-sm mt-4", "text-muted-foreground")}
|
|
>
|
|
* Third-party model pricing includes a 20% gateway markup.
|
|
Prices synced daily from upstream providers.
|
|
</p>
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* FAQ Section */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<h2 className="text-3xl font-bold mb-8">
|
|
Frequently Asked Questions
|
|
</h2>
|
|
<div className="grid md:grid-cols-2 gap-8">
|
|
<div>
|
|
<h3 className="font-semibold mb-2">
|
|
What counts as a token?
|
|
</h3>
|
|
<p className={cn("text-sm", "text-muted-foreground")}>
|
|
Tokens are pieces of text that our models process. On
|
|
average, 1 token is about 4 characters or 0.75 words in
|
|
English. Both input and output tokens are counted toward
|
|
your usage.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold mb-2">
|
|
Can I upgrade or downgrade my plan?
|
|
</h3>
|
|
<p className={cn("text-sm", "text-muted-foreground")}>
|
|
Yes, you can change your plan at any time. Upgrades take
|
|
effect immediately, and downgrades take effect at the
|
|
start of your next billing cycle.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold mb-2">
|
|
What happens if I exceed my token limit?
|
|
</h3>
|
|
<p className={cn("text-sm", "text-muted-foreground")}>
|
|
You'll be charged at the pay-as-you-go rate for
|
|
additional tokens. We'll notify you when you're
|
|
approaching your limit so there are no surprises.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold mb-2">
|
|
Is my data used to train models?
|
|
</h3>
|
|
<p className={cn("text-sm", "text-muted-foreground")}>
|
|
By default, your data is not used for training. Team and
|
|
Enterprise plans have explicit data exclusion guarantees.
|
|
See our privacy policy for details.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* CTA */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
className="mt-20 text-center"
|
|
>
|
|
<h2 className="text-2xl font-semibold mb-4">
|
|
Ready to get started?
|
|
</h2>
|
|
<p
|
|
className={cn(
|
|
"mb-8 max-w-2xl mx-auto",
|
|
"text-muted-foreground"
|
|
)}
|
|
>
|
|
Start building with Zoo Industries today. Every new account gets $5
|
|
free credit.
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<a
|
|
href="https://console.zoo.ngo"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Button
|
|
className={cn(
|
|
"bg-primary text-primary-foreground hover:bg-primary/90"
|
|
)}
|
|
>
|
|
Start Building Free
|
|
</Button>
|
|
</a>
|
|
<Link href="/contact">
|
|
<Button
|
|
variant="outline"
|
|
className={cn(
|
|
"border-border text-foreground hover:bg-accent"
|
|
)}
|
|
>
|
|
Talk to Sales
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|