Files

179 lines
6.2 KiB
TypeScript

'use client'
import { useRef } from 'react'
import Link from 'next/link'
import { ArrowRight } from 'lucide-react'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
import { Text, View, XStack, YStack } from '@hanzo/ui'
import { CREATIVE_MODELS } from '@/lib/creative-models'
import { NOW } from '@/lib/zen'
import { Section } from './chrome'
/**
* Every model, side by side and named as released (Zen 6, Kai 1, Koma 1): the
* language models (Enso, Kai, Zen) and the
* creative ones (Koma, Iro, Mori, Oto, Koe, Uta), each a panel that opens wide
* under the pointer onto its painting (public/images/models, painted on Hanzo's
* own engine), what it does in a line, and a link to its page, where its
* benchmarks are. The panel a reader points at opens; with
* none pointed at, Enso's stands open. Koma's panel plays its reel while open
* under the pointer. Below 1024px the panels are a row to swipe, every one open.
* The opening is CSS (`[data-models]` in app/layout.tsx), still under reduced
* motion. A model's name is said as released; what it is and what it does are
* authored (`phrase`) and drawn in the reader's language.
*/
interface Model {
slug: string
name: string
kind: string
line: string
href: string
still: string
}
const still = (slug: string) => `/images/models/${slug}.webp`
const LANGUAGE: readonly Model[] = [
{ slug: 'enso', name: 'Enso 1', kind: phrase('Meta-LLM'), line: phrase("Enso is a meta-LLM: Hanzo's own small model that coordinates 500+ models for higher intelligence, faster and at lower cost."), href: '/enso', still: still('enso') },
{ slug: 'kai', name: 'Kai 1', kind: phrase('Decisions'), line: phrase('Deterministic probability distributions with sub-millisecond GPU latency and zero output tokens to pay for.'), href: '/kai', still: still('kai') },
{ slug: 'zen', name: NOW.name, kind: phrase('Open models'), line: phrase('Open-weight models for agentic coding. Download the weights or run them on Hanzo Cloud.'), href: '/zen', still: still('zen') },
]
const CREATIVE: readonly Model[] = CREATIVE_MODELS.map((m) => ({
slug: m.slug,
name: m.release,
kind: m.slug === 'oto' ? phrase('Sound') : m.modalityLabel,
line: m.lede,
href: `/${m.slug}`,
still: still(m.slug),
}))
const MODELS = [...LANGUAGE, ...CREATIVE]
const LEAD = 'enso'
/** Koma's reel, played while its panel is open under the pointer. */
function Reel({ still }: { still: string }) {
const ref = useRef<HTMLVideoElement>(null)
return (
<video
ref={ref}
data-media=""
muted
loop
playsInline
preload="none"
poster={still}
aria-hidden
onPointerEnter={() => void ref.current?.play().catch(() => {})}
onPointerLeave={() => ref.current?.pause()}
>
<source src="/videos/koma-demo.webm" type="video/webm" />
<source src="/videos/koma-demo.mp4" type="video/mp4" />
</video>
)
}
function Panel({ m }: { m: Model }) {
const { t } = useI18n()
const kind = t(m.kind)
return (
<View
data-model={m.slug}
data-lead={m.slug === LEAD ? '' : undefined}
render={<Link href={m.href} aria-label={t('{name}, {kind}', { name: m.name, kind })} />}
position="relative"
overflow="hidden"
rounded={20}
borderWidth={1}
borderColor="var(--border)"
bg="var(--pure-black)"
$platform-web={{ borderRadius: '20px', textDecoration: 'none' }}
>
{m.slug === 'koma' ? (
<Reel still={m.still} />
) : (
<View
data-media=""
render={<img src={m.still} alt="" loading="lazy" />}
position="absolute"
t={0}
l={0}
width="100%"
height="100%"
$platform-web={{ objectFit: 'cover' }}
/>
)}
<View
data-scrim=""
aria-hidden
position="absolute"
t={0}
l={0}
r={0}
b={0}
pointerEvents="none"
$platform-web={{ background: 'linear-gradient(to top, color-mix(in srgb, var(--pure-black) 82%, transparent) 0%, transparent 58%)' }}
/>
<Text
data-spine=""
aria-hidden
fontFamily="$mono"
fontSize="$2"
fontWeight="600"
color="var(--pure-white)"
position="absolute"
b={22}
l="50%"
$platform-web={{ transform: 'translateX(-50%) rotate(180deg)', writingMode: 'vertical-rl', whiteSpace: 'nowrap' }}
>
{m.name}
</Text>
<YStack data-open="" position="absolute" l={0} r={0} b={0} p={24} gap={8}>
<Text fontFamily="$mono" fontSize="$1" color="var(--white-80)">
{kind}
</Text>
<Text fontSize="$11" lineHeight="$11" fontWeight="600" color="var(--pure-white)" $platform-web={{ letterSpacing: '-0.035em' }}>
{m.name}
</Text>
<Text fontSize="$3" lineHeight="1.5rem" color="var(--white-80)" maxW={420}>
{t(m.line)}
</Text>
<XStack items="center" gap={6} mt={6}>
<Text fontSize="$2" fontWeight="600" color="var(--pure-white)">
{t('Explore {name}', { name: m.name })}
</Text>
<ArrowRight size={14} color="var(--pure-white)" aria-hidden data-i18n-mirror />
</XStack>
</YStack>
</View>
)
}
/** Every model, the language ones alone (`language`), or the creative ones (`creative`, as /studio shows them). */
export default function Models({ creative, language }: { creative?: boolean; language?: boolean }) {
const { t } = useI18n()
const shown = creative ? CREATIVE : language ? LANGUAGE : MODELS
return (
<Section
id={creative ? 'creative' : 'models'}
label={creative ? 'Hanzo.Creative' : 'Hanzo.Models'}
title={t(creative ? 'Every creative model, in one studio.' : 'Every kind of model, one API.')}
lede={t(
creative
? 'Video, image, 3D, sound, voice and music, each a node on the canvas. Open one to see what it makes.'
: language
? 'Every model behind one key.'
: 'Language, routing, decisions and creative models. Open one to see what it does, and where it is measured.',
)}
>
<View data-models="" width="100%">
{shown.map((m) => (
<Panel key={m.slug} m={m} />
))}
</View>
</Section>
)
}