Files
zeekayandGitHub e20dc71ad8 i18n: /s* to /z*, the app's rooms and settings read their copy through t (#69)
The product pages from /s3 to /zap, /university and its course data,
/trust, /security, /sdks, /support, /tasks, /startups, /sms-opt-in, the
buying specialist, Zen, Kai's guide, Studio, the settings pages, and the
shared sales, solutions, zap and ui pieces. Client code draws with t(),
server components with <T>, copy held as data is marked with phrase()
and drawn through t() where it lands. A link or a figure inside a
sentence is a value, so a translator reorders the whole sentence.

Rebased on main: every page main moved onto Fold or Steps keeps main's
layout, with its words wrapped (label, title, lede, points). Steps now
draws a step's body through t, as it already drew the title. Settings
keeps main's usage panel, refresh, rewards wallet and sign-in wallet
rows, each label wrapped.

Left English: the skills page's install line (a command and two URLs),
and the solution eyebrow and sibling heading built from a kind label.

Claude-Session: https://claude.ai/code/session_015XYKLjZtDTJEnMo4eK3AFH
2026-10-29 23:51:44 -07:00

238 lines
8.1 KiB
TypeScript

'use client'
/**
* The generations, read by scrolling. A rail on a wide screen; the numerals
* stay in each milestone on a phone. Active is ink and weight. Nothing here
* is hidden by opacity, and reduced motion still has every word.
*/
import { useEffect, useState } from 'react'
import { Button, Text, XStack, YStack } from '@hanzo/ui'
import { More } from '@hanzo/ui/marketing'
import { Figure } from './figures'
import { LINEAGE, type GenStatus, type GenerationStep } from './line'
import { FLASH, NEXT } from '@/lib/zen'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
const BRANCHES = [phrase('Language'), phrase('Vision'), phrase('Audio'), phrase('Image'), phrase('Retrieval'), phrase('Safety')] as const
const PATH = [phrase('Train'), phrase('Attest'), phrase('Evaluate'), phrase('Release')] as const
/** A generation's status, as the page says it. */
const STATUS: Record<GenStatus, string> = {
Historical: phrase('Historical'),
'Open weights': phrase('Open weights'),
Retired: phrase('Retired'),
Shipped: phrase('Shipped'),
'Served only': phrase('Served only'),
Current: phrase('Current'),
'Research preview': phrase('Research preview'),
}
type Say = ReturnType<typeof useI18n>['t']
export function Lineage() {
const { t } = useI18n()
const [active, setActive] = useState('zen6')
useEffect(() => {
const nodes = [...document.querySelectorAll<HTMLElement>('[data-gen]')]
if (!nodes.length) return
const io = new IntersectionObserver(
(entries) => {
const hit = entries
.filter((e) => e.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]
const id = hit?.target.getAttribute('data-gen')
if (id) setActive(id)
},
{ rootMargin: '-40% 0px -45% 0px', threshold: [0, 0.25, 0.6] },
)
nodes.forEach((n) => io.observe(n))
return () => io.disconnect()
}, [])
const go = (id: string) => {
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches
document.getElementById(anchor(id))?.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' })
}
return (
<XStack gap="$8" width="100%" items="flex-start">
<YStack
render="nav"
aria-label={t('Zen generations')}
display="none"
position="sticky"
t={112}
self="flex-start"
gap="$1"
shrink={0}
borderLeftWidth={1}
borderColor="var(--border)"
pl="$3"
$lg={{ display: 'flex' }}
>
{LINEAGE.map((step) => {
const on = active === step.id
return (
<Button
key={step.id}
variant="ghost"
size="sm"
aria-current={on ? 'true' : undefined}
aria-label={label(step, t)}
justify="flex-start"
onClick={() => go(step.id)}
>
<Text
fontFamily="$mono"
fontSize={step.bridge ? '$1' : '$2'}
fontWeight={on ? '700' : '500'}
color={on ? '$ink' : 'var(--muted-foreground)'}
>
{step.mark}
</Text>
</Button>
)
})}
</YStack>
<YStack flex={1} minW={0} gap="$2">
{LINEAGE.map((step) => (
<Milestone key={step.id} step={step} hot={active === step.id} />
))}
</YStack>
</XStack>
)
}
function anchor(id: string): string {
return id === 'zen7' ? 'zen7' : `gen-${id}`
}
function label(step: GenerationStep, t: Say): string {
if (step.id === 'zen7') return t('{name}, research preview, not currently available', { name: step.name })
return t('{name}, {status}', { name: step.name, status: t(STATUS[step.status]) })
}
function Milestone({ step, hot }: { step: GenerationStep; hot: boolean }) {
const { t } = useI18n()
const preview = step.id === 'zen7'
return (
<YStack
id={anchor(step.id)}
data-gen={step.id}
gap="$4"
py={preview ? '$12' : step.bridge ? '$6' : '$10'}
borderWidth={0}
borderTopWidth={1}
borderColor="var(--border)"
borderStyle={preview ? 'dashed' : 'solid'}
$platform-web={{ scrollMarginTop: '96px' }}
>
<Text
color={hot || step.status === 'Current' ? '$ink' : 'var(--muted-foreground)'}
fontWeight={hot || step.status === 'Current' ? '600' : '500'}
$platform-web={{
fontSize: step.bridge ? 'clamp(2rem, 4vw, 2.75rem)' : 'clamp(3.25rem, 7vw, 5.5rem)',
letterSpacing: '-0.045em',
lineHeight: '0.9',
}}
>
{step.mark}
</Text>
<YStack gap="$2" maxW={640}>
<XStack gap="$3" flexWrap="wrap" items="baseline">
<Text render="h3" fontSize={step.bridge ? '$5' : '$7'} lineHeight={step.bridge ? '$5' : '$7'} fontWeight="600" color="$ink">
{step.name}
</Text>
{step.period ? (
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)">
{step.period}
</Text>
) : null}
</XStack>
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" textTransform="uppercase" color={preview ? '$ink' : 'var(--muted-foreground)'}>
{t(STATUS[step.status])}
</Text>
<Text fontSize="$5" lineHeight="$5" color="$ink">
{t(step.theme)}
</Text>
<Text fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
{t(step.summary, { name: step.name, flash: FLASH.name })}
</Text>
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)">
{t(step.shape)}
</Text>
</YStack>
<Figure kind={step.figure} hot={hot || step.status === 'Current'} />
{step.figure === 'branch' ? (
<XStack flexWrap="wrap" gap="$3">
{BRANCHES.map((name) => (
<Text key={name} fontFamily="$mono" fontSize="$1" lineHeight="$1" textTransform="uppercase" color="var(--muted-foreground)">
{t(name)}
</Text>
))}
</XStack>
) : null}
{step.servedIds.length ? (
<Text fontFamily="$mono" fontSize="$2" lineHeight="$2" color="$ink">
{step.servedIds.join(' ')}
</Text>
) : null}
{step.references?.length ? (
<XStack flexWrap="wrap" gap="$3">
{step.references.map((ref) => (
<More key={ref.href} href={ref.href}>
{ref.label}
</More>
))}
</XStack>
) : null}
{step.id === 'zen6' ? <More href="#zen6">{t('The current generation')}</More> : null}
{preview ? (
<YStack gap="$4" maxW={560} pt="$4">
<Text render="p" fontSize="$6" lineHeight="$6" fontWeight="500" color="$ink">
{t('What comes next is still research.')}
</Text>
<YStack gap="$3">
{PATH.map((stage, i) => (
<XStack key={stage} gap="$3" items="baseline">
<Text
fontFamily="$mono"
fontSize="$2"
color={i < 2 ? '$ink' : 'var(--muted-foreground)'}
borderWidth={0}
borderBottomWidth={i > 1 ? 1 : 0}
borderStyle="dashed"
borderColor="var(--muted-foreground)"
>
{t(stage)}
</Text>
{i > 1 ? (
<Text fontSize="$2" color="var(--muted-foreground)">
{t('Not published')}
</Text>
) : null}
</XStack>
))}
</YStack>
<Text fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
{t('The path to a release is training, then attestation, then evaluation, then publication. Nothing past the preview is published.')}
</Text>
<XStack gap="$4" flexWrap="wrap">
<More href={NEXT.request}>{t('Request research access')}</More>
<More href="https://huggingface.co/zenlm">{t('The family on Hugging Face')}</More>
</XStack>
</YStack>
) : null}
</YStack>
)
}