mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
A reader who had never chosen a theme got dark whatever the device said: the
head's boot script was seeded with `dark`. The install's default is now
`system` (lib/appearance.ts), painted before the first frame, and the client
applies it once mounted, which is what installs the listener that follows the
device when it changes.
/zen's generations, the timeline marks and the Train/Attest/Evaluate/Release
underlines set `borderStyle` with one side's width, so the other three sides
drew at the browser's medium width: every generation was a 3px box with its
text against the edge. Each now sets `borderWidth={0}` before its side.
Gate e2e/gates/scheme.spec.ts: a first visit under a light and a dark device
paints that scheme and follows a change, and /zen's generations read
1px 0 0 0. The look gate still passes.
Claude-Session: https://claude.ai/code/session_01SCPMGSQsLNF2YtHFL4bwyp
221 lines
7.3 KiB
TypeScript
221 lines
7.3 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 GenerationStep } from './line'
|
|
import { NEXT } from '@/lib/zen'
|
|
|
|
const BRANCHES = ['Language', 'Vision', 'Audio', 'Image', 'Retrieval', 'Safety'] as const
|
|
const PATH = ['Train', 'Attest', 'Evaluate', 'Release'] as const
|
|
|
|
export function Lineage() {
|
|
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="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)}
|
|
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): string {
|
|
if (step.id === 'zen7') return `${step.name}, research preview, not currently available`
|
|
return `${step.name}, ${step.status}`
|
|
}
|
|
|
|
function Milestone({ step, hot }: { step: GenerationStep; hot: boolean }) {
|
|
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)'}>
|
|
{step.status}
|
|
</Text>
|
|
<Text fontSize="$5" lineHeight="$5" color="$ink">
|
|
{step.theme}
|
|
</Text>
|
|
<Text fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
|
|
{step.summary}
|
|
</Text>
|
|
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)">
|
|
{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)">
|
|
{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">The current generation</More> : null}
|
|
|
|
{preview ? (
|
|
<YStack gap="$4" maxW={560} pt="$4">
|
|
<Text render="p" fontSize="$6" lineHeight="$6" fontWeight="500" color="$ink">
|
|
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)"
|
|
>
|
|
{stage}
|
|
</Text>
|
|
{i > 1 ? (
|
|
<Text fontSize="$2" color="var(--muted-foreground)">
|
|
Not published
|
|
</Text>
|
|
) : null}
|
|
</XStack>
|
|
))}
|
|
</YStack>
|
|
<Text fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
|
|
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}>Request research access</More>
|
|
<More href="https://huggingface.co/zenlm">The family on Hugging Face</More>
|
|
</XStack>
|
|
</YStack>
|
|
) : null}
|
|
</YStack>
|
|
)
|
|
}
|