Files
zeekayandGitHub 949608fd9d i18n: pages and components c–l draw their copy through @hanzo/i18n (#67)
Calculator, captable, careers, cloud, compare, contact and contact-sales,
customers, dashboards, dataroom, datastore, dev, download, enso,
enterprise, extension, family, integrations, investors, kai, learn,
legal and their components say their words through t() in client code,
<T> in server components and phrase() where copy is held as data; one
sentence is one key, figures and links its arguments.

Rebased on main: in every conflict main's file stands (its copy, layout
and styling) and only the wrapping is added back, including copy main
added since.

Claude-Session: https://claude.ai/code/session_015XYKLjZtDTJEnMo4eK3AFH
2026-10-30 01:15:31 -07:00

111 lines
3.6 KiB
TypeScript

import type { ReactNode } from 'react'
import { Check } from 'lucide-react'
import { phrase } from '@hanzo/i18n'
import { T } from '@hanzo/i18n/react'
import { Box, Text, XStack, YStack } from '@/components/ui'
import { Eyebrow, More, Title } from '@hanzo/ui/marketing'
import { Grid } from '@hanzo/ui/grid'
import { Card } from '@/components/marketing/band'
import { ProviderMark } from '@hanzo/app/ProviderMark'
/**
* Enso against Zen, on /models: how each one is had. Enso is ours and answers
* on Hanzo Cloud only; Zen's weights are published. What each family does is
* Family's, above it, so neither card says it again. A server component, all
* gui props.
*/
const SIDES: readonly { name: string; model: string; org: string; terms: string; points: readonly string[]; href: string; lit?: boolean }[] = [
{
name: 'Enso',
model: 'hanzo',
org: 'hanzo',
terms: phrase('Proprietary · Hanzo Cloud only'),
points: [phrase('Flash, Pro and Ultra presets'), phrase('OpenAI- and Anthropic-compatible'), phrase('Metered and audited per organization')],
href: '/enso',
lit: true,
},
{
name: 'Zen',
model: 'zen6',
org: 'zoo',
terms: phrase('Open weights · Zoo Labs Foundation'),
points: [phrase('Free to download and self-host'), phrase('Or call it on Hanzo Cloud'), phrase('From a 501(c)(3) non-profit')],
href: '/zen',
},
]
function Point({ children }: { children: ReactNode }) {
return (
<Text render="li" display="flex" items="center" gap="$2" fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
<Text display="inline-flex" shrink={0} color="$ink">
<Check size={16} />
</Text>
{children}
</Text>
)
}
export function Versus() {
return (
<Box
render="section"
aria-labelledby="versus-title"
py="$11"
px="$4"
$sm={{ px: '$5' }}
$lg={{ px: '$6' }}
borderTopWidth={1}
borderColor="var(--border)"
>
<Grid columns={1} gap={40} width="100%" maxW={1024} mx="auto">
<Grid columns={1} gap={14} width="100%" maxW={672} mx="auto" $platform-web={{ textAlign: 'center' }}>
<Eyebrow>
<T>Enso vs Zen</T>
</Eyebrow>
<Title id="versus-title">
<T>Proprietary orchestration, on open foundations</T>
</Title>
</Grid>
<Grid columns={{ min: 300, max: 2 }} gap={24}>
{SIDES.map((s) => (
<Card
key={s.name}
display="flex"
flexDirection="column"
gap="$5"
p="$6"
borderColor={s.lit ? 'var(--border-strong)' : 'var(--border)'}
bg={s.lit ? '$hover' : '$panel'}
>
<XStack items="center" gap="$3">
<ProviderMark org={s.org} model={s.model} ink />
<YStack>
<Text render="h3" fontSize="$6" lineHeight="$6" fontWeight="600" color="$ink">
{s.name}
</Text>
<Text fontSize="$2" lineHeight="$2" color="var(--muted-foreground)">
<T>{s.terms}</T>
</Text>
</YStack>
</XStack>
<Text render="ul" display="grid" gap="$2" m={0} p={0}>
{s.points.map((p) => (
<Point key={p}>
<T>{p}</T>
</Point>
))}
</Text>
<More href={s.href} mt="auto">
<T values={{ name: s.name }}>{'Explore {name} →'}</T>
</More>
</Card>
))}
</Grid>
</Grid>
</Box>
)
}
export default Versus