mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
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
238 lines
8.1 KiB
TypeScript
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>
|
|
)
|
|
}
|