Files
hanzo.ai/components/zen/lineage.tsx
zeekay b54fe45c68 Follow the device's light or dark scheme, and draw /zen's generations as rules
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
2026-10-08 11:52:12 -07:00

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>
)
}