Files
zeekay 997a3bf703 /calculator loads its token calculator after the first screen
The shared chunks grew under it and took the page to 408 KB against
perf.budget.json's 400; the calculator stands below the fold, as on
/enterprise, so its script loads after the page's own.
2026-10-17 04:13:01 -07:00

989 lines
44 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useMemo, useState, type ReactNode } from 'react'
import dynamic from 'next/dynamic'
import { Text, View, XStack, YStack } from '@hanzo/ui'
import { Models } from '@/components/api/count'
// The token calculator stands below the first screen: its script loads after it.
const Savings = dynamic(() => import('@/components/sales/Savings'))
// True Savings — an operating-cost model, not a TAM calculator. Every number on
// this page is derived from a visible, editable control; nothing is hardcoded
// into the headline. Two axes are kept deliberately separate: COVERAGE (how much
// of a category Hanzo replaces, which carries weight) and EVIDENCE (how well we
// can prove it, which carries none). Collapsing them would let an attestation
// read as a verification, which is the one thing this page must never do.
const FTE = 220_000 // fully loaded engineer, used for both glue reclaimed and Hanzo ops
const REF_E = 100 // reference deployment the analyst priced against
const REF_D = 30
type Cov = 'full' | 'partial' | 'experimental' | 'roadmap'
type Ev = 'verified' | 'gap' | 'internal' | 'attested' | 'repo'
const WEIGHT: Record<Cov, number> = { full: 1, partial: 0.5, experimental: 0.25, roadmap: 0 }
const COV_LABEL: Record<Cov, string> = {
full: 'full',
partial: 'partial 50%',
experimental: 'experimental 25%',
roadmap: 'roadmap 0%',
}
const EV_LABEL: Record<Ev, string> = {
verified: 'verified',
gap: 'verified gap',
internal: 'internal production',
attested: 'management-attested',
repo: 'repository-verified',
}
const COV_CYCLE: Cov[] = ['full', 'partial', 'experimental', 'roadmap']
// Billing basis, so the model still means something away from the reference
// deployment: per-seat rows scale on employees, per-engineer rows on engineers,
// platform-fee rows not at all. At E=100/30% every factor is 1.0, which is what
// reproduces the analyst's $613k software total exactly.
type Basis = 's' | 'e' | 'f'
type Row = {
name: string
replaces: string
hanzo: string
k: number // analyst default, $k/yr at the reference deployment
basis: Basis
cov: Cov
ev: Ev
}
const ROWS: Row[] = [
{ name: 'SSO & workforce identity', replaces: 'Okta / Auth0', hanzo: 'iam · zero-trust · authz', k: 10, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Customer identity (product)', replaces: 'Auth0 B2C', hanzo: 'iam (multi-tenant, per-org CEK)', k: 14, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Secrets & KMS', replaces: 'Vault Cloud', hanzo: 'kms', k: 11, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'Observability', replaces: 'Datadog / New Relic', hanzo: 'o11y · metrics', k: 54, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'Error tracking', replaces: 'Sentry', hanzo: 'o11y (errortracking)', k: 9, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'Feature flags', replaces: 'LaunchDarkly', hanzo: 'flags', k: 7, basis: 'e', cov: 'full', ev: 'repo' },
{ name: 'Product analytics', replaces: 'Amplitude / Mixpanel', hanzo: 'analytics', k: 15, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Event routing / CDP', replaces: 'Segment', hanzo: 'destinations · tracker', k: 22, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'Git hosting', replaces: 'GitHub Enterprise', hanzo: 'git (git.hanzo.ai)', k: 8, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'CI/CD', replaces: 'CircleCI / Actions', hanzo: 'hanzoai/ci · arc runners', k: 18, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'Container registry', replaces: 'ECR / Docker Hub', hanzo: 'registry (oci.hanzo.ai)', k: 4, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'PaaS / app hosting', replaces: 'Vercel / Heroku', hanzo: 'paas · deploy · functions', k: 13, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'App backend / BaaS', replaces: 'Supabase / Firebase', hanzo: 'base · docdb · orm', k: 15, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Queues & streaming', replaces: 'Confluent / SQS', hanzo: 'pubsub (NATS) · kafka', k: 10, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'AI gateway & routing', replaces: 'Portkey / OpenRouter', hanzo: 'ai · zen · gateway', k: 12, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Workflow automation', replaces: 'Zapier', hanzo: 'automations · connectorruntime · cron', k: 12, basis: 's', cov: 'full', ev: 'repo' },
{ name: 'Billing & metering', replaces: 'Chargebee / Metronome', hanzo: 'billing · usage · entitlements · pricing', k: 18, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Commerce', replaces: 'Shopify Plus', hanzo: 'commerce · marketplace', k: 24, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'E-signature', replaces: 'DocuSign', hanzo: 'sign', k: 2, basis: 's', cov: 'full', ev: 'repo' },
{ name: 'Cap table', replaces: 'Carta', hanzo: 'captable', k: 5, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Data rooms', replaces: 'DocSend / iDeals', hanzo: 'dataroom', k: 6, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'Referrals & affiliates', replaces: 'PartnerStack', hanzo: 'affiliates · referrals · link', k: 9, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'Experiments & evals', replaces: 'Statsig / Optimizely', hanzo: 'experiments · evals', k: 12, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'Zero-trust access', replaces: 'Tailscale / CF Access', hanzo: 'zero-trust · ingress', k: 7, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Search', replaces: 'Algolia / Elastic', hanzo: 'index · websearch', k: 12, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'Vector store', replaces: 'Pinecone', hanzo: 'knowledge', k: 8, basis: 'f', cov: 'partial', ev: 'gap' },
{ name: 'Compliance automation', replaces: 'Vanta / Drata', hanzo: 'compliance · audit · sbom · security', k: 18, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'CRM', replaces: 'Salesforce / HubSpot', hanzo: 'crm · company', k: 27, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Marketing automation', replaces: 'HubSpot Marketing', hanzo: 'marketing · campaign', k: 10, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'Projects & tasks', replaces: 'Linear / Asana', hanzo: 'projects · tasks', k: 14, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Knowledge & docs', replaces: 'Notion / Confluence', hanzo: 'knowledge · guide', k: 12, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Support & messaging', replaces: 'Intercom / Zendesk', hanzo: 'help · ask · channels · bots', k: 11, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Notifications & email', replaces: 'SendGrid / Customer.io', hanzo: 'notify', k: 7, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Incident & status', replaces: 'PagerDuty / Statuspage', hanzo: 'o11y (incident · status)', k: 11, basis: 'e', cov: 'full', ev: 'internal' },
{ name: 'Finance ops & ledger', replaces: 'NetSuite add-ons', hanzo: 'books · treasury · ledger-fi', k: 6, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'Design & creative', replaces: 'Figma / Canva', hanzo: 'studio (studio.hanzo.ai)', k: 16, basis: 's', cov: 'full', ev: 'repo' },
{ name: 'Video, calls & calendar', replaces: 'Zoom / Calendly', hanzo: 'team (calls · calendar)', k: 18, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Office suite & drive', replaces: 'Google Workspace', hanzo: 'office · drive', k: 14, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'HR & payroll', replaces: 'Gusto / Rippling', hanzo: 'hr · payroll', k: 48, basis: 's', cov: 'full', ev: 'internal' },
{ name: 'Payment orchestration', replaces: 'PSP markup over interchange+', hanzo: 'psp · x402 · wallets', k: 12, basis: 'f', cov: 'full', ev: 'repo' },
{ name: 'ERP / CMS / business apps', replaces: 'NetSuite / WordPress', hanzo: 'erp · base', k: 20, basis: 'f', cov: 'full', ev: 'internal' },
{ name: 'LLM inference & routing', replaces: 'OpenAI / Anthropic API', hanzo: 'enso (routing) · zen (self-hosted)', k: 36, basis: 'e', cov: 'full', ev: 'attested' },
]
const AI_ROW = ROWS.length - 1
// One chip per vendor that shows up on a real bill — several per category. A
// category counts while any of its vendors is selected; the counter counts
// vendors, not categories. 107 chips, which is the same 107 dependencies the
// reference deployment consolidated.
const VENDORS: [string, number][] = [
['Okta', 0], ['Auth0', 0], ['JumpCloud', 0], ['Clerk', 1], ['Firebase Auth', 1], ['Vault', 2], ['AWS KMS', 2],
['Doppler', 2], ['Datadog', 3], ['New Relic', 3], ['Grafana Cloud', 3], ['Splunk', 3], ['Sentry', 4], ['Rollbar', 4],
['LaunchDarkly', 5], ['Split', 5], ['Amplitude', 6], ['Mixpanel', 6], ['Heap', 6], ['Segment', 7], ['RudderStack', 7],
['GitHub', 8], ['GitLab', 8], ['Bitbucket', 8], ['CircleCI', 9], ['Buildkite', 9], ['Docker Hub', 10], ['JFrog', 10],
['Vercel', 11], ['Netlify', 11], ['Heroku', 11], ['Fly.io', 11], ['Supabase', 12], ['Firebase', 12], ['Confluent', 13],
['Redpanda', 13], ['Portkey', 14], ['OpenRouter', 14], ['Zapier', 15], ['Make', 15], ['n8n cloud', 15],
['Chargebee', 16], ['Metronome', 16], ['Stripe Billing', 16], ['Shopify', 17], ['BigCommerce', 17], ['DocuSign', 18],
['Dropbox Sign', 18], ['Carta', 19], ['Pulley', 19], ['DocSend', 20], ['iDeals', 20], ['PartnerStack', 21],
['Rewardful', 21], ['Statsig', 22], ['Optimizely', 22], ['Tailscale', 23], ['CF Access', 23], ['Algolia', 24],
['Elastic Cloud', 24], ['Pinecone', 25], ['Weaviate', 25], ['Vanta', 26], ['Drata', 26], ['Salesforce', 27],
['HubSpot CRM', 27], ['Pipedrive', 27], ['HubSpot Marketing', 28], ['Mailchimp', 28], ['Klaviyo', 28], ['Linear', 29],
['Asana', 29], ['Jira', 29], ['Monday', 29], ['Notion', 30], ['Confluence', 30], ['Zendesk', 31], ['Intercom', 31],
['Front', 31], ['SendGrid', 32], ['Twilio', 32], ['Customer.io', 32], ['PagerDuty', 33], ['Statuspage', 33],
['Opsgenie', 33], ['QuickBooks', 34], ['Bill.com', 34], ['Figma', 35], ['Canva', 35], ['Adobe CC', 35], ['Zoom', 36],
['Calendly', 36], ['Loom', 36], ['Google Workspace', 37], ['Microsoft 365', 37], ['Dropbox', 37], ['Gusto', 38],
['Rippling', 38], ['Deel', 38], ['Stripe', 39], ['Braintree', 39], ['NetSuite', 40], ['WordPress VIP', 40],
['Contentful', 40], ['OpenAI API', 41], ['Anthropic API', 41], ['Azure OpenAI', 41],
]
const SEAMS = [
'auth bridges', 'webhooks', 'data sync', 'billing reconciliation', 'entitlements', 'audit exports',
'pipelines', 'vendor o11y', 'incident routing', 'schema transforms', 'secret rotation', 'API migrations',
'identity lifecycle', 'model fallbacks', 'security reviews',
]
const SECOND_ORDER = [
'SSO price premiums', 'per-vendor security reviews', 'DPAs', 'procurement cycles', 'duplicate observability',
'license reclamation', 'credential sprawl', 'integration attack surface', 'onboarding speed', 'unified policy',
'common metering', 'model portability', 'lock-in exposure', 'air-gapped deployment', 'incident response speed',
'fewer failure domains',
]
const money = (n: number) => {
const a = Math.abs(n)
const sign = n < 0 ? '−' : ''
if (a >= 1e6) return `${sign}$${(a / 1e6).toFixed(3).replace(/\.?0+$/, '')}M`
return `${sign}$${Math.round(a / 1e3).toLocaleString('en-US')}k`
}
const dollars = (n: number) =>
`${n < 0 ? '−' : ''}$${Math.round(Math.abs(n)).toLocaleString('en-US')}`
export default function CalculatorPage() {
const [E, setE] = useState(100)
const [Dp, setDp] = useState(30)
const [cloud, setCloud] = useState(15) // $k / month
const [infraPct, setInfraPct] = useState(60)
const [glueFte, setGlueFte] = useState(1.5)
const [opsFte, setOpsFte] = useState(0.5)
// The three deductions. Default 0 means "not included until entered" — the
// fully-adjusted tier stays explicitly incomplete rather than quietly equal
// to the run rate.
const [retained, setRetained] = useState(0)
const [fee, setFee] = useState(0)
const [migration, setMigration] = useState(0)
const [migYears, setMigYears] = useState(3)
const [cov, setCov] = useState<Cov[]>(() => ROWS.map((r) => r.cov))
const [price, setPrice] = useState<Record<number, number>>({}) // user overrides, $k
const [von, setVon] = useState<boolean[]>(() => VENDORS.map(() => true))
// AI sub-calculator — off by default so the headline stays the analyst's
// figure until the user prices their own bill.
const [aiOpen, setAiOpen] = useState(false)
const [aiOn, setAiOn] = useState(false)
const [ai, setAi] = useState({
spend: 10, // $k / month
routable: 60, // % of addressable spend that can move to a cheaper model
selfHost: 50, // % of routed spend that can run on Zen
ensoSave: 55, // % saved by routing alone
zenCost: 12, // Zen cost as % of frontier price
frontier: 25, // % that must stay frontier — pass-through, never counted
overhead: 10, // eval + fallback overhead against gross saving
gpuUtil: 70, // % GPU utilization
batch: 40, // % of self-hosted work that is batch
})
const D = Math.round((E * Dp) / 100)
const aiSaving = useMemo(() => {
const annual = ai.spend * 1000 * 12
const passthrough = annual * (ai.frontier / 100)
const addressable = annual - passthrough
const routed = addressable * (ai.routable / 100)
const hosted = routed * (ai.selfHost / 100)
const routedOnly = routed - hosted
// Low utilization raises effective self-hosted unit cost; batch work lowers it.
const zenRatio = (ai.zenCost / 100) * (70 / Math.max(ai.gpuUtil, 1)) * (1 - 0.25 * (ai.batch / 100))
const gross = hosted * Math.max(1 - zenRatio, 0) + routedOnly * (ai.ensoSave / 100)
return Math.max(gross * (1 - ai.overhead / 100), 0)
}, [ai])
const on = useMemo(
() => ROWS.map((_, i) => VENDORS.some(([, c], k) => c === i && von[k])),
[von],
)
const value = useMemo(
() =>
ROWS.map((r, i) => {
if (price[i] !== undefined) return price[i] * 1000
if (i === AI_ROW && aiOn) return aiSaving
const factor = r.basis === 's' ? E / REF_E : r.basis === 'e' ? D / REF_D : 1
return r.k * 1000 * factor
}),
[price, aiOn, aiSaving, E, D],
)
const software = useMemo(
() => value.reduce((sum, v, i) => (on[i] ? sum + v * WEIGHT[cov[i]] : sum), 0),
[value, on, cov],
)
const infra = cloud * 1000 * 12 * (infraPct / 100)
const glue = glueFte * FTE
const ops = opsFte * FTE
const gross = software + infra + glue
const runRate = gross - ops
const amortized = migration > 0 ? migration / Math.max(migYears, 1) : 0
const adjusted = runRate - retained - fee - amortized
const hasDeductions = retained > 0 || fee > 0 || migration > 0
const vendorCount = von.filter(Boolean).length
const cycleCov = (i: number) =>
setCov((c) => {
const next = [...c]
next[i] = COV_CYCLE[(COV_CYCLE.indexOf(c[i]) + 1) % COV_CYCLE.length]
return next
})
const toggleRow = (i: number, checked: boolean) =>
setVon((v) => v.map((s, k) => (VENDORS[k][1] === i ? checked : s)))
const setAllVendors = (v: boolean) => setVon(VENDORS.map(() => v))
const reset = () => {
setCov(ROWS.map((r) => r.cov))
setPrice({})
setVon(VENDORS.map(() => true))
setAiOn(false)
}
const num = (id: string, v: number, set: (n: number) => void, step = 1000) => (
<Field id={id} min={0} step={step} value={v || ''} placeholder="0" onChange={(n) => set(n)} />
)
return (
<YStack bg="var(--background)" $platform-web={{ color: 'var(--foreground)', lineHeight: '1.5' }}>
<YStack width="100%" maxW="72rem" mx="auto" pt="2.5rem" px="1.25rem" pb="5rem" gap="2rem">
<YStack render="header">
<Text render="h1" display="block" fontSize="2rem" lineHeight="2.1rem" letterSpacing="$12" fontWeight="600" color="var(--foreground)" $md={{ fontSize: '2.75rem', lineHeight: '2.8875rem' }}>
Hanzo True Savings
</Text>
<Lead>Price the assembly tax.</Lead>
<Text render="p" display="block" mt="0.6rem" fontSize="0.95rem" lineHeight={lh(0.95)} fontWeight="500" color="$ink">
100+ capabilities · <Models /> · one identity plane · one API · one operating platform.
</Text>
<Text render="p" display="block" mt="0.5rem" maxW="min(62ch, 100%)" fontSize="0.95rem" lineHeight={lh(0.95)} color={DIM}>
This is not a TAM calculator. It models operating expenses a company can eliminate, reduce,
or reclaim. Every assumption is visible and editable.
</Text>
</YStack>
<Card render="section" accent>
<H2>Reference deployment — Hanzo runs on Hanzo</H2>
<Note>
Fully off GCP · Hanzo-managed infrastructure · AI, cloud, identity, observability, deploy,
storage and business-ops native · eligible AI routed through Enso and self-hosted Zen ·{' '}
<B>107 vendor and managed-service dependencies consolidated into one plane</B> · ~90% lower
compute and eligible-inference costs in selected workloads.
</Note>
<Label>Founder-attested.</Label>
</Card>
<YStack render="section" id="tokens" gap="1rem">
<H2>Your AI bill, three ways</H2>
<Note>
The tokens you use a month at the providers&apos; list prices, on Hanzo Cloud, and on GPUs of your own. Every rate
is read from our price list when the page opens; the volumes are yours.
</Note>
<Savings />
</YStack>
<YStack render="section">
<H2>Your deployment</H2>
<Controls>
<Range id="ts-e" label="Employees" shows={E} min={10} max={2000} step={10} value={E} onChange={setE} />
<Range id="ts-d" label="Engineers" shows={`${Dp}% (${D})`} min={10} max={60} step={5} value={Dp} onChange={setDp} />
<Range id="ts-c" label="Cloud bill / month" shows={`$${cloud}k`} min={0} max={250} step={5} value={cloud} onChange={setCloud} />
<Range id="ts-g" label="Integration-glue FTEs" shows={glueFte} min={0} max={20} step={0.5} value={glueFte} onChange={setGlueFte} />
<Range id="ts-o" label="Platform-ops FTEs (running Hanzo)" shows={opsFte} min={0} max={6} step={0.5} value={opsFte} onChange={setOpsFte} />
<Range id="ts-i" label="Infrastructure delta" shows={`${infraPct}%`} min={0} max={90} step={5} value={infraPct} onChange={setInfraPct} />
</Controls>
</YStack>
<YStack render="section">
<H2>Deductions — not included until entered</H2>
<Controls>
<Card>
<Caption id="ts-r">Retained vendor spending ($/yr)</Caption>
{num('ts-r', retained, setRetained)}
</Card>
<Card>
<Caption id="ts-f">Hanzo managed / support fee ($/yr)</Caption>
{num('ts-f', fee, setFee)}
</Card>
<Card>
<Caption id="ts-m">One-time migration cost ($)</Caption>
{num('ts-m', migration, setMigration)}
</Card>
<Range id="ts-y" label="Amortized over" shows={`${migYears} yr`} min={1} max={5} step={1} value={migYears} onChange={setMigYears} />
</Controls>
</YStack>
<YStack render="section">
<H2>Results</H2>
<View display="grid" gridTemplateColumns="repeat(auto-fit, minmax(15rem, 1fr))" gap="0.9rem">
<Tier label="1 · Gross addressable savings" note="Software + infrastructure + glue, before any deduction.">
<Big>{money(gross)}</Big>
</Tier>
<Tier lead label="2 · Annual run-rate savings" note="Gross − Hanzo operating labor. This is the headline.">
<Big>{money(runRate)}</Big>
</Tier>
<Tier label="3 · Fully adjusted annual savings" note="Run-rate − retained vendors − Hanzo fees − amortized migration.">
{hasDeductions ? (
<Big>{money(adjusted)}</Big>
) : (
<Text display="block" py="0.5rem" fontSize="1rem" lineHeight="1.2rem" color={WARN}>
Customer input required
</Text>
)}
</Tier>
<Tier label="4 · Three-year economic value" note="Fully adjusted × 3, with migration amortized across the horizon.">
<Big>{money(adjusted * 3)}</Big>
</Tier>
</View>
<View display="grid" gridTemplateColumns="repeat(auto-fit, minmax(11rem, 1fr))" gap="0.75rem" mt="0.9rem">
<Part label="Software replaced (selected rows)">{money(software)}</Part>
<Part label="Infrastructure delta">{money(infra)}</Part>
<Part label="Glue engineering reclaimed">{money(glue)}</Part>
<Part label="Cost of operating Hanzo" cost>
−{money(ops)}
</Part>
<Part label="Per employee / yr (run-rate)">{dollars(runRate / Math.max(E, 1))}</Part>
<Part label="≈ senior engineers funded">{(runRate / FTE).toFixed(1)}</Part>
<Part label="Vendors collapsed">{vendorCount} → 1</Part>
</View>
<Text render="p" display="flex" flexWrap="wrap" gap="0.4rem" mt="0.9rem" fontSize="0.8rem" lineHeight={lh(0.8)} color={DIM}>
<Code>gross = software + infra + glue</Code> · <Code>run-rate = gross − ops</Code> ·{' '}
<Code>adjusted = run-rate − retained − fees − migration ÷ years</Code>
</Text>
<Text render="p" display="block" mt="0.5rem" fontSize="0.85rem" lineHeight={lh(0.85)} color={WARN}>
Do not present {money(runRate)} as fully net until the final three deductions are entered.
</Text>
<Text render="p" display="block" mt="1rem" maxW="min(80ch, 100%)" borderLeftWidth={2} borderColor="$edge" pl="0.9rem" fontSize="0.8rem" lineHeight={lh(0.8)} color={DIM}>
Hanzo True Savings is an interactive operating-cost model, not a quote, guarantee, audit, or
promise of results. Default prices are editable estimates based on representative technology
stacks. Actual savings depend on existing contracts, usage, architecture, migration
requirements, retained vendors, staffing, workload eligibility, and Hanzo deployment
choices. Hanzo&rsquo;s internal case-study figures are management-attested unless explicitly
marked independently verified.
</Text>
</YStack>
<YStack render="section">
<H2>Click everything you pay for</H2>
<Note>
Every vendor maps to a line in the table. Tap the ones on your bill — the totals and the{' '}
<B>vendors → 1</B> counter follow. <Toggle onPress={() => setAllVendors(true)}>select all</Toggle>{' '}
<Toggle onPress={() => setAllVendors(false)}>clear</Toggle>
</Note>
<XStack flexWrap="wrap" gap="0.35rem" mt="0.7rem">
{VENDORS.map(([name], k) => (
<Toggle key={name + k} pressed={von[k]} onPress={() => setVon((v) => v.map((s, j) => (j === k ? !s : s)))}>
{name}
</Toggle>
))}
</XStack>
</YStack>
<YStack render="section">
<H2>Category by category</H2>
<Note>
Two axes, kept separate. <B>Coverage</B> carries weight and is clickable — full 100%,
partial 50%, experimental 25%, roadmap 0%. <B>Evidence</B> carries no weight. A service can
have full coverage while still management-attested. Separating the axes prevents an
attestation from being presented as verification.
</Note>
<Note>
Prices are the analyst defaults at the reference deployment (100 employees, 30% engineers)
and are editable — type over any of them. Untouched rows scale on their billing basis:
per-seat with employees, per-engineer with engineers, platform fees not at all.{' '}
<Toggle onPress={reset}>Reset to analyst defaults</Toggle>
</Note>
<View overflowX="auto" bg="$panel" borderWidth={1} borderColor="$edge" mt="0.8rem">
<Text render="table" width="100%" fontSize="0.85rem" lineHeight={lh(0.85)} color="$ink" $platform-web={{ display: 'table', borderCollapse: 'collapse' }}>
<thead>
<tr>
<Th label="Include" />
<Th>Category</Th>
<Th>Replaces (typical)</Th>
<Th>Hanzo services</Th>
<Th>Coverage</Th>
<Th>Evidence</Th>
<Th right>$k / yr</Th>
</tr>
</thead>
<tbody>
{ROWS.map((r, i) => {
const off = !on[i]
const last = i === ROWS.length - 1 && !aiOpen
return (
<tr key={r.name}>
<Td off={off} last={last}>
<input type="checkbox" checked={on[i]} aria-label={`Include ${r.name}`} onChange={(e) => toggleRow(i, e.target.checked)} />
</Td>
<Td off={off} last={last}>
{r.name}
{i === AI_ROW && (
<>
{' '}
<Text
render={<button type="button" aria-expanded={aiOpen} onClick={() => setAiOpen((o) => !o)} />}
py={0}
px="0.2rem"
bg="transparent"
borderWidth={0}
fontSize="0.68rem" lineHeight={lh(0.68)}
letterSpacing={0.55}
textTransform="uppercase"
textDecorationLine="underline"
color={SAVE}
cursor="pointer"
>
{aiOpen ? 'hide' : 'sub-calculator'}
</Text>
</>
)}
</Td>
<Td off={off} last={last}>
{r.replaces}
</Td>
<Td off={off} last={last} mono>
{r.hanzo}
</Td>
<Td off={off} last={last}>
<Chip ink={COV_INK[cov[i]]} title="Click to regrade coverage" onPress={() => cycleCov(i)}>
{COV_LABEL[cov[i]]}
</Chip>
</Td>
<Td off={off} last={last}>
<Chip ink={EV_INK[r.ev]}>{EV_LABEL[r.ev]}</Chip>
</Td>
<Td off={off} last={last} right>
<Field
id={`ts-price-${i}`}
label={`Annual estimate for ${r.name} in thousands`}
min={0}
value={Math.round(value[i] / 1000)}
narrow
onChange={(n) => setPrice((p) => ({ ...p, [i]: n }))}
/>
{WEIGHT[cov[i]] < 1 && (
<Text ml="0.3rem" fontFamily="$mono" fontSize="0.72rem" lineHeight={lh(0.72)} color={WARN}>
×{WEIGHT[cov[i]]}
</Text>
)}
</Td>
</tr>
)
})}
{aiOpen && (
<tr>
<Td colSpan={7} last wrap ground>
<Text render="h3" display="block" mb="0.4rem" fontSize="0.95rem" lineHeight={lh(0.95)} fontWeight="600" color="$ink">
AI sub-calculator
</Text>
<Note>
Hanzo&rsquo;s current deployment reports reductions approaching 90% for selected
workloads routed to lower-cost or self-hosted models. Enter your own eligible
workload percentage rather than applying 90% to the entire AI bill. Hanzo routes <Models />,
but model count is not the economic claim — the control plane is.
</Note>
<Controls my={11}>
{([
['spend', 'AI spend / month ($k)', 0, 500, 1],
['frontier', 'Must stay frontier (%)', 0, 100, 5],
['routable', 'Routable to cheaper models (%)', 0, 100, 5],
['selfHost', 'Of routed, self-hostable (%)', 0, 100, 5],
['ensoSave', 'Enso routing saving (%)', 0, 90, 5],
['zenCost', 'Zen cost vs frontier (%)', 1, 100, 1],
['gpuUtil', 'GPU utilization (%)', 10, 100, 5],
['batch', 'Batch vs interactive mix (%)', 0, 100, 5],
['overhead', 'Eval / fallback overhead (%)', 0, 50, 5],
] as const).map(([key, label, min, max, step]) => (
<Range
key={key}
id={`ts-ai-${key}`}
label={label}
shows={ai[key]}
min={min}
max={max}
step={step}
value={ai[key]}
onChange={(n) => setAi((a) => ({ ...a, [key]: n }))}
/>
))}
</Controls>
<Note>
Computed eligible saving <B mono>{money(aiSaving)}</B> / yr against an analyst default of{' '}
<B mono>{money(ROWS[AI_ROW].k * 1000)}</B>. Frontier pass-through is never counted as a saving.{' '}
<Toggle pressed={aiOn} onPress={() => setAiOn((v) => !v)}>
{aiOn ? 'using computed figure' : 'use computed figure'}
</Toggle>
</Note>
</Td>
</tr>
)}
</tbody>
<tfoot>
<tr>
<Td colSpan={6} foot>
Weighted total — replaced software
</Td>
<Td right foot mono>
{money(software)}
</Td>
</tr>
</tfoot>
</Text>
</View>
</YStack>
<YStack render="section">
<H2>Infrastructure delta</H2>
<Note>
The {infraPct}% delta above is a simplified default and a{' '}
<B>user-controlled assumption, not a universal benchmark</B>. The egress anchor is real —
DigitalOcean at $0.01/GiB against an AWS example at $0.09/GB — but it supports an egress
comparison; it does not prove {infraPct}% for every workload. A full model would price
provider, compute, GPU, database, storage, egress, observability, support, existing
commitments, target Hanzo cost and migration overlap separately.
</Note>
</YStack>
<YStack render="section">
<H2>Glue engineering</H2>
<Note>
The seams a vendor stack requires someone to own: {SEAMS.join(', ')}. The default of 1.5 FTE
at ${(FTE / 1000).toFixed(0)}k fully loaded is conservative for a 30+ vendor estate, and
0.5 FTE of platform operations is subtracted on the other side.
</Note>
<Label>Do not say Hanzo eliminates engineering. Hanzo redirects it from vendor seams to product.</Label>
</YStack>
<YStack render="section">
<H2>The bill is the cheap half</H2>
<Note>
Everything above prices what you stop <B>paying</B>. It ignores what you would have to{' '}
<B>build</B> to own the whole thing yourself. Public anchors: Auth0 acquired for ~$6.5B ·
Datadog ~$1.55B of 2025 R&amp;D · Zapier 800+ people and 9,000+ integrations. These are not
additive valuations; they show each category is a substantial engineering domain. A
replacement-cost study of the full repository estate is <B>under analysis</B> — no number
will appear here until that report lands.
</Note>
</YStack>
<YStack render="section">
<H2>Exclusions — what this model never counts</H2>
<Note>
Card interchange and network fees (the payment row counts PSP markup only) · frontier model
pass-through, unless the workload is routed, distilled or self-hosted · deep vertical
software (clinical, CAD, EDA, airline operations, industrial control, scientific
instruments, jurisdiction-specific record systems) · migration coexistence, where both
stacks run at once · retained vendors, since every row is independently deselectable.
</Note>
</YStack>
<YStack render="section">
<H2>Market context</H2>
<Note>
Okta 2026: 98 applications on average, 259 at companies with 2,000+ employees, 72 below
that. Zylo 2026 SaaS Management Index: a median of $9,455 of SaaS spend per employee, with
36% of licenses unused. The ${Math.round(613)}k software default at the reference deployment
works out to $6,130 per employee — below Zylo&rsquo;s enterprise-skewed median, so the model
is conservative relative to the benchmark. None of this implies all SaaS spend is
recoverable.
</Note>
</YStack>
<YStack render="section">
<H2>Second-order benefits — real, unpriced</H2>
<Note>{SECOND_ORDER.join(' · ')}.</Note>
<Label>Material, but outside the headline until a defensible measurement exists.</Label>
</YStack>
<Card render="section" accent>
<H2>Replace the stack, not the capability.</H2>
<Note>
100+ capabilities · <Models /> · one identity · one API · one platform · self-host or
managed.
</Note>
<Lead>Select your current stack and price the assembly tax.</Lead>
</Card>
</YStack>
</YStack>
)
}
/** The page's one rhythm: a line is half again its size. */
const lh = (rem: number) => `${+(rem * 1.5).toFixed(4)}rem` as const
/* ── the page's parts ──────────────────────────────────────────────────────
The model reads in the site's own tokens: a saving in the success hue, a
cost in the error hue, a caveat in the secondary ink — so it follows the
theme the reader picked, with no palette of its own. */
const SAVE = '$good'
const COST = '$bad'
const WARN = 'var(--text-secondary)'
const DIM = 'var(--muted-foreground)'
const GREY = 'var(--text-tertiary)'
/** The tint a highlighted tier and a pressed vendor sit on. */
const SOFT = '$good/12'
const COV_INK: Record<Cov, ChipInk> = { full: SAVE, partial: WARN, experimental: COST, roadmap: GREY }
const EV_INK: Record<Ev, ChipInk> = { verified: SAVE, gap: COST, internal: GREY, repo: GREY, attested: WARN }
type ChipInk = typeof SAVE | typeof COST | typeof WARN | typeof GREY
function H2({ children }: { children: ReactNode }) {
return (
<Text render="h2" display="block" mb="0.5rem" fontSize="1.05rem" lineHeight={lh(1.05)} letterSpacing={-0.17} fontWeight="600" color="var(--foreground)">
{children}
</Text>
)
}
function Lead({ children }: { children: ReactNode }) {
return (
<Text render="p" display="block" mt="0.4rem" fontSize="1.15rem" lineHeight={lh(1.15)} color={SAVE}>
{children}
</Text>
)
}
function Note({ children }: { children: ReactNode }) {
return (
<Text render="p" display="block" maxW="min(76ch, 100%)" fontSize="0.88rem" lineHeight={lh(0.88)} color={DIM}>
{children}
</Text>
)
}
/** The emphasis inside a note: full ink, and the mono face for a figure. */
function B({ mono, children }: { mono?: boolean; children: ReactNode }) {
return (
<Text render="b" lineHeight="unset" color="$ink" fontFamily={mono ? '$mono' : undefined} $platform-web={mono ? { fontVariantNumeric: 'tabular-nums' } : undefined}>
{children}
</Text>
)
}
function Code({ children }: { children: ReactNode }) {
return (
<Text render="code" lineHeight="unset" fontFamily="$mono" fontSize="0.66rem" color="$ink">
{children}
</Text>
)
}
function Label({ children }: { children: ReactNode }) {
return (
<Text render="p" display="block" mt="0.5rem" fontSize="0.72rem" lineHeight={lh(0.72)} letterSpacing={1.04} textTransform="uppercase" color={WARN}>
{children}
</Text>
)
}
/** A panel; `accent` rules its left edge in the saving hue. */
function Card({ render, accent, children }: { render?: 'section'; accent?: boolean; children: ReactNode }) {
return (
<YStack
render={render}
bg="$panel"
borderWidth={1}
borderColor="$edge"
borderLeftWidth={accent ? 3 : 1}
borderLeftColor={accent ? SAVE : '$edge'}
py="0.8rem"
px="1rem"
gap="0.4rem"
>
{children}
</YStack>
)
}
/** The grid the controls sit in: as many 14rem columns as fit. */
function Controls({ my, children }: { my?: number; children: ReactNode }) {
return (
<View display="grid" gridTemplateColumns="repeat(auto-fit, minmax(14rem, 1fr))" gap="0.9rem" my={my}>
{children}
</View>
)
}
/** A control's caption, in small caps; `shows` is its value at the right. */
function Caption({ id, shows, children }: { id: string; shows?: ReactNode; children: ReactNode }) {
return (
<Text
render={<label htmlFor={id} />}
display="flex"
justify="space-between"
gap="0.5rem"
fontSize="0.7rem" lineHeight={lh(0.7)}
letterSpacing={1.01}
textTransform="uppercase"
color={DIM}
>
<Text lineHeight="unset" color="inherit">{children}</Text>
{shows !== undefined ? (
<Text render="output" lineHeight="unset" fontFamily="$mono" textTransform="none" letterSpacing={0} color="$ink">
{shows}
</Text>
) : null}
</Text>
)
}
/** A slider in a card, its caption above it. */
function Range({
id,
label,
shows,
min,
max,
step,
value,
onChange,
}: {
id: string
label: string
shows: ReactNode
min: number
max: number
step: number
value: number
onChange: (n: number) => void
}) {
return (
<Card>
<Caption id={id} shows={shows}>
{label}
</Caption>
<View
render={<input id={id} type="range" min={min} max={max} step={step} value={value} onChange={(e) => onChange(+e.target.value)} />}
width="100%"
$platform-web={{ accentColor: 'var(--state-success)' }}
/>
</Card>
)
}
/** A number field; `narrow` for a price cell in the table. */
function Field({
id,
label,
min,
step,
value,
placeholder,
narrow,
onChange,
}: {
id: string
label?: string
min: number
step?: number
value: number | ''
placeholder?: string
narrow?: boolean
onChange: (n: number) => void
}) {
return (
<Text
render={
<input
id={id}
type="number"
aria-label={label}
min={min}
step={step}
value={value}
placeholder={placeholder}
onChange={(e) => onChange(Math.max(Number(e.target.value) || 0, 0))}
/>
}
display={narrow ? undefined : 'block'}
width={narrow ? '5.5rem' : '100%'}
py="0.3rem"
px="0.45rem"
text={narrow ? 'right' : undefined}
bg="var(--background)"
borderWidth={1}
borderColor="$edge"
fontFamily="$mono"
fontSize="0.85rem" lineHeight={lh(0.85)}
color="$ink"
/>
)
}
/** One of the four tiers; `lead` is the headline, on the saving tint. */
function Tier({ label, note, lead, children }: { label: string; note: string; lead?: boolean; children: ReactNode }) {
return (
<YStack bg={lead ? SOFT : '$panel'} borderWidth={1} borderColor={lead ? SAVE : '$edge'} py="1rem" px="1.1rem" gap="0.3rem">
<Text fontSize="0.7rem" lineHeight={lh(0.7)} letterSpacing={1.01} textTransform="uppercase" color={DIM}>
{label}
</Text>
{children}
<Text render="small" fontSize="0.76rem" lineHeight={lh(0.76)} color={DIM}>
{note}
</Text>
</YStack>
)
}
/** A tier's figure: the saving hue, in lining mono figures, tight. */
function Big({ children }: { children: ReactNode }) {
return (
<Text
display="block"
fontFamily="$mono"
fontSize="1.7rem"
lineHeight="1.785rem"
letterSpacing={-1.06}
color={SAVE}
$md={{ fontSize: '2.4rem', lineHeight: '2.52rem' }}
$platform-web={{ fontVariantNumeric: 'tabular-nums' }}
>
{children}
</Text>
)
}
/** One part of the run-rate, ruled across its top in the saving hue — or the cost hue for what Hanzo costs. */
function Part({ label, cost, children }: { label: string; cost?: boolean; children: ReactNode }) {
return (
<YStack bg="$panel" borderWidth={1} borderColor="$edge" borderTopWidth={3} borderTopColor={cost ? COST : SAVE} py="0.7rem" px="0.9rem">
<Text render="small" display="block" mb="0.2rem" fontSize="0.72rem" lineHeight={lh(0.72)} color={DIM}>
{label}
</Text>
<Text fontFamily="$mono" fontSize="1.15rem" lineHeight={lh(1.15)} color="$ink" $platform-web={{ fontVariantNumeric: 'tabular-nums' }}>
{children}
</Text>
</YStack>
)
}
/** A vendor, or a small action set the same way; pressed reads in the saving hue on its tint. */
function Toggle({ pressed, onPress, children }: { pressed?: boolean; onPress: () => void; children: ReactNode }) {
return (
<Text
render={<button type="button" aria-pressed={pressed} onClick={onPress} />}
py="0.28rem"
px="0.55rem"
borderWidth={1}
borderColor={pressed ? SAVE : '$edge'}
bg={pressed ? SOFT : '$panel'}
fontSize="0.78rem" lineHeight={lh(0.78)}
color={pressed ? SAVE : '$ink'}
cursor="pointer"
>
{children}
</Text>
)
}
/** A grade: a word in an outline, in its grade's ink; a button where the grade can be changed. */
function Chip({ ink, title, onPress, children }: { ink: ChipInk; title?: string; onPress?: () => void; children: ReactNode }) {
return (
<Text
render={onPress ? <button type="button" title={title} onClick={onPress} /> : 'span'}
py="0.12rem"
px="0.45rem"
borderWidth={1}
borderColor={ink}
bg="transparent"
fontFamily="$body"
fontSize="0.66rem" lineHeight={lh(0.66)}
letterSpacing={0.53}
textTransform="uppercase"
whiteSpace="nowrap"
color={ink}
cursor={onPress ? 'pointer' : undefined}
>
{children}
</Text>
)
}
/** A table cell, stated a table cell: a gui Text is an inline box until told. */
const CELL = { '$platform-web': { display: 'table-cell' } } as const
/** A column head: small caps, held at the top while the table scrolls. */
function Th({ right, label, children }: { right?: boolean; label?: string; children?: ReactNode }) {
return (
<Text
render={<th aria-label={label} />}
{...CELL}
position="sticky"
t={0}
z="var(--z-raised)"
py="0.45rem"
px="0.7rem"
text={right ? 'right' : 'left'}
whiteSpace="nowrap"
borderBottomWidth={1}
borderColor="$edge"
bg="$panel"
fontSize="0.65rem" lineHeight={lh(0.65)}
letterSpacing={0.94}
textTransform="uppercase"
color={DIM}
>
{children}
</Text>
)
}
/** A body cell. `off` dims a row the reader left out; `foot` is the total under the table. */
function Td({
right,
mono,
off,
last,
wrap,
ground,
foot,
colSpan,
children,
}: {
right?: boolean
mono?: boolean
off?: boolean
last?: boolean
wrap?: boolean
ground?: boolean
foot?: boolean
colSpan?: number
children?: ReactNode
}) {
return (
<Text
render={<td colSpan={colSpan} />}
{...CELL}
py={ground ? '1rem' : '0.45rem'}
px={ground ? '1rem' : '0.7rem'}
text={right ? 'right' : 'left'}
whiteSpace={wrap ? 'normal' : 'nowrap'}
borderBottomWidth={last || foot ? 0 : 1}
borderTopWidth={foot ? 2 : 0}
borderColor={foot ? '$ink' : '$edge'}
bg={ground ? 'var(--background)' : undefined}
opacity={off ? 0.42 : 1}
fontFamily={mono ? '$mono' : undefined}
fontSize={mono && !foot ? '0.76rem' : undefined}
lineHeight={mono && !foot ? lh(0.76) : lh(0.85)}
fontWeight={foot ? '700' : undefined}
color={mono && !foot ? DIM : '$ink'}
$platform-web={mono ? { display: 'table-cell', fontVariantNumeric: 'tabular-nums' } : CELL['$platform-web']}
>
{children}
</Text>
)
}