mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Every word the landing, the header menus, the phone sheet, the footer,
the help corner and the way in (sign-in, sign-up, the reset, the
invitation, the callback, onboarding, the 404) write goes through
@hanzo/i18n's t(): one sentence is one message, its figures, names and
links arguments; copy written as data is phrase('…') where written and
t(label) where drawn. Plans, the chart, the API states and the lines
lib/kai and lib/enso build for the landing do the same.
Right to left: <html lang dir> is set before the first paint (the
package's bootScript first in the head); a figure, clock, range or
price outside a sentence is isolated; sides are logical gui props, the
layout's [data-join] corners logical, a bar grows from the start;
arrows along the line mirror (Onward for "words ->" links). The repo
dates read in the reader's language.
Gates select by slot, never by an English label (rtl, help). New gate
translated.spec.ts: in Arabic the landing at 1440 and 390, every menu
open, the phone sheet, sign-in, sign-up and the app signed in hold no
authored English, nothing scrolls sideways, every range is isolated;
French the same for English's commonest words. What another package
draws with no words prop is listed by name (@hanzo/ui's Footer label,
@hanzo/build's composer row and suggestions).
@hanzo/i18n 0.5.3 (the catalogs: 1,443 keys, every offered locale
complete), @hanzo/app 0.2.1 (the app's words). The link gate imports
the terms versions from @hanzo/app, the one SDK main now resolves.
204 lines
9.2 KiB
TypeScript
204 lines
9.2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
import { Check, RotateCcw } from 'lucide-react'
|
|
import { useI18n } from '@hanzo/i18n/react'
|
|
import { Button, Text, View, XStack, YStack, type GuiElement } from '@hanzo/ui'
|
|
import { More } from '@hanzo/ui/marketing'
|
|
import { Mark } from '@/components/models/Mark'
|
|
import snapshot from '@/lib/data/benchmarks-coding.json' with { type: 'json' }
|
|
import { useCoding } from '@/hooks/useCoding'
|
|
import { derive, range, times, LEVEL } from '@/lib/coding'
|
|
import type { Run } from '@/lib/kai'
|
|
import { race, type Lane } from '@/lib/home'
|
|
import { Reading, Section, Win, node, useSeen, useStill } from './chrome'
|
|
import { Onward } from './label'
|
|
|
|
/**
|
|
* Hanzo Dev beside Claude Code on the same held-out repairs, from the research
|
|
* API's coding.repair runs (lib/coding): the build's snapshot first, then the
|
|
* API's answer once it arrives (hooks/useCoding).
|
|
*
|
|
* A REPLAY OF THE RECORDED TIMINGS, NOT A TRANSCRIPT. The runs store each
|
|
* side's median seconds a task, its model calls a task and its solved count;
|
|
* no per-task transcript is filed. So each window runs a clock and a bar for
|
|
* exactly the median the run records, at the speed it ran, and names the run
|
|
* it replays. Under reduced motion both stand at their last frame. Under the
|
|
* clocks, the paired speed over the tasks both solved and each path Hanzo Dev
|
|
* settled a task by that is a resolved gain, with its own speed and interval,
|
|
* as /benchmarks prints them; both solved counts only while Hanzo Dev solved
|
|
* more (lib/home `race`).
|
|
*
|
|
* In the reader's language, every sentence is one message with its figures as
|
|
* arguments; a clock, a speed and an interval drawn on their own keep their own
|
|
* order on a right-to-left page (`data-i18n-ltr`), and each bar grows from the
|
|
* start of the line.
|
|
*/
|
|
const INITIAL = derive(snapshot.data as unknown as Run[])
|
|
|
|
/** Seconds as the clock prints them. */
|
|
const secs = (v: number) => `${v.toFixed(2)} s`
|
|
|
|
function Track({ lane, solved, take, play, still }: { lane: Lane; solved: boolean; take: number; play: boolean; still: boolean }) {
|
|
const { t, dir } = useI18n()
|
|
const bar = useRef<GuiElement>(null)
|
|
const clock = useRef<GuiElement>(null)
|
|
const [ended, setDone] = useState(false)
|
|
const done = still || ended
|
|
const run = useRef<Animation | null>(null)
|
|
const isClaude = lane.name.toLowerCase().includes('claude')
|
|
const icon = isClaude ? <Mark org="claude" size={15} ink /> : <Mark org="hanzo" size={15} ink />
|
|
|
|
// The bar is a paused animation from the first frame, so it waits at zero without a style of its own.
|
|
useLayoutEffect(() => {
|
|
const el = node(bar)
|
|
if (!el || typeof el.animate !== 'function') return
|
|
const a = el.animate([{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }], { duration: lane.seconds * 1000, easing: 'linear', fill: 'both' })
|
|
run.current = a
|
|
if (still) a.finish()
|
|
else a.pause()
|
|
return () => a.cancel()
|
|
}, [lane.seconds, still])
|
|
|
|
useEffect(() => {
|
|
const a = run.current
|
|
const face = node(clock)
|
|
if (!a || !face) return
|
|
if (still) {
|
|
face.textContent = secs(lane.seconds)
|
|
return
|
|
}
|
|
a.currentTime = 0
|
|
face.textContent = secs(0)
|
|
if (!play) return
|
|
a.play()
|
|
const reset = requestAnimationFrame(() => setDone(false))
|
|
let raf = 0
|
|
const tick = () => {
|
|
const t = Math.min(lane.seconds, Number(a.currentTime ?? 0) / 1000)
|
|
face.textContent = secs(t)
|
|
if (t < lane.seconds) raf = requestAnimationFrame(tick)
|
|
}
|
|
raf = requestAnimationFrame(tick)
|
|
a.onfinish = () => {
|
|
face.textContent = secs(lane.seconds)
|
|
setDone(true)
|
|
}
|
|
return () => {
|
|
cancelAnimationFrame(reset)
|
|
cancelAnimationFrame(raf)
|
|
a.onfinish = null
|
|
}
|
|
}, [take, play, still, lane.seconds])
|
|
|
|
return (
|
|
<Win title={lane.name} icon={icon} aside={done ? t('done') : play ? t('working') : t('ready')} slot={`lane-${lane.name}`}>
|
|
<YStack p={14} gap={12} $md={{ p: 20, gap: 14 }}>
|
|
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)">
|
|
{t('median time a task')}
|
|
</Text>
|
|
<XStack items="center" justify="space-between" gap={12}>
|
|
<Text ref={clock} fontFamily="$mono" fontSize="1.75rem" lineHeight="2rem" $md={{ fontSize: '2.5rem', lineHeight: '2.75rem' }} fontWeight="600" color="var(--foreground)" $platform-web={{ fontVariantNumeric: 'tabular-nums' }} data-clock data-i18n-ltr>
|
|
{secs(lane.seconds)}
|
|
</Text>
|
|
<View
|
|
aria-hidden
|
|
opacity={done ? 1 : 0}
|
|
width={28}
|
|
height={28}
|
|
rounded={9999}
|
|
bg="var(--secondary)"
|
|
items="center"
|
|
justify="center"
|
|
$platform-web={{ borderRadius: '9999px', transition: 'opacity 0.2s ease-in-out' }}
|
|
>
|
|
<Check size={16} strokeWidth={2.25} color="var(--foreground)" />
|
|
</View>
|
|
</XStack>
|
|
<View height={10} rounded={9999} borderWidth={1} borderColor="var(--border-strong)" overflow="hidden" bg="var(--muted)" $platform-web={{ borderRadius: '9999px' }}>
|
|
<View ref={bar} height="100%" rounded={9999} bg="var(--foreground)" $platform-web={{ borderRadius: '9999px', transformOrigin: dir === 'rtl' ? 'right center' : 'left center' }} />
|
|
</View>
|
|
<YStack gap={2} pt={4}>
|
|
{lane.calls != null ? (
|
|
<Reading label={t('model calls a task')}>
|
|
<bdi dir="ltr">{lane.calls.toFixed(1)}</bdi>
|
|
</Reading>
|
|
) : null}
|
|
{solved ? <Reading label={t('solved')}>{t('{solved} of {of}', { solved: lane.solved, of: lane.of })}</Reading> : null}
|
|
</YStack>
|
|
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)" $platform-web={{ overflowWrap: 'anywhere' }}>
|
|
{t('replay of run {run}', { run: lane.run })}
|
|
</Text>
|
|
</YStack>
|
|
</Win>
|
|
)
|
|
}
|
|
|
|
export default function Race() {
|
|
const { t } = useI18n()
|
|
const c = useCoding(INITIAL)
|
|
const r = useMemo(() => race(c), [c])
|
|
const [ref, seen] = useSeen(0.35)
|
|
const still = useStill()
|
|
const [take, setTake] = useState(0)
|
|
if (!r) return null
|
|
return (
|
|
<Section
|
|
id="race"
|
|
label="Coding.Tasks"
|
|
title={t('{speed} faster on the same coding tasks.', { speed: times(r.speed) })}
|
|
lede={t("Hanzo Dev and Claude Code were handed the same {n} coding tasks (single-fault repairs), one try each. Each clock replays that side's median time a task, at the speed it ran.", { n: r.base.of })}
|
|
>
|
|
<YStack gap={16}>
|
|
<View ref={ref} display="grid" gridTemplateColumns="minmax(0, 1fr) minmax(0, 1fr)" gap={8} $md={{ gap: 16 }} data-slot="race">
|
|
<Track lane={r.base} solved={r.solved} take={take} play={seen} still={still} />
|
|
<Track lane={r.ours} solved={r.solved} take={take} play={seen} still={still} />
|
|
</View>
|
|
<XStack justify="center" pt={4}>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
rounded={9999}
|
|
$platform-web={{ borderRadius: '9999px', display: 'inline-flex', alignItems: 'center', gap: '6px' }}
|
|
disabled={!seen && !still}
|
|
onPress={() => setTake((t) => t + 1)}
|
|
>
|
|
<RotateCcw size={13} aria-hidden />
|
|
{t('Replay race')}
|
|
</Button>
|
|
</XStack>
|
|
</YStack>
|
|
<XStack flexWrap="wrap" items="center" justify="space-between" gap={24}>
|
|
<YStack gap={6} maxW={720} flex={1} minW={260}>
|
|
<Text fontSize="$5" lineHeight="$5" color="var(--foreground)" data-figure="speed">
|
|
{t('{speed} faster over the {n} tasks both solved, {level} interval {range}.', { speed: times(r.speed), n: r.speed.n ?? '', level: LEVEL, range: range(r.speed) })}
|
|
</Text>
|
|
{r.paths.length ? (
|
|
<Text fontSize="$3" lineHeight="$3" color="var(--muted-foreground)">
|
|
{r.solved ? `${t('Claude Code solved {base} of {of}; Hanzo Dev {ours} of {all}.', { base: r.base.solved, of: r.base.of, ours: r.ours.solved, all: r.ours.of })} ` : ''}
|
|
{t('By how Hanzo Dev settled a task:')}
|
|
</Text>
|
|
) : null}
|
|
<YStack render="ul" $platform-web={{ listStyle: 'none', margin: 0, padding: 0 }} data-figure="paths">
|
|
{r.paths.map((p) => (
|
|
<XStack key={p.label} render="li" justify="space-between" flexWrap="wrap" columnGap={16} py={4} borderTopWidth={1} borderColor="var(--border)" data-path={p.label}>
|
|
<Text fontSize="$2" lineHeight="$2" color="var(--foreground)">
|
|
{t(p.n === 1 ? '{label}, {n} task' : '{label}, {n} tasks', { label: t(p.label), n: p.n ?? '' })}
|
|
</Text>
|
|
<Text fontFamily="$mono" fontSize="$2" lineHeight="$2" color="var(--muted-foreground)" $platform-web={{ fontVariantNumeric: 'tabular-nums' }} data-i18n-ltr>
|
|
{times(p)} · {LEVEL} {range(p)}
|
|
</Text>
|
|
</XStack>
|
|
))}
|
|
</YStack>
|
|
</YStack>
|
|
<XStack gap={16} items="center" flexWrap="wrap">
|
|
<More href="/benchmarks#coding-agent">
|
|
<Onward>{t('Every coding task and both runs')}</Onward>
|
|
</More>
|
|
</XStack>
|
|
</XStack>
|
|
</Section>
|
|
)
|
|
}
|