mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
The index, its boards and the shelf's pages wrap their prose: server pages with <T>, the boards and the library with t(), and copy held in data with phrase(). Figures read from the runs are values, and the fields lib/kai, lib/tools, lib/coding and lib/tally hand a board are drawn through t(), so they translate once those files emit keys. The shared parts (Chapter, Fold, Stat, Tile, Panel, Mark) translate a string where they draw it and take a node from a server page. figures.spec lets the index import @hanzo/i18n: words in, words out, never a figure.
912 lines
32 KiB
TypeScript
912 lines
32 KiB
TypeScript
'use client'
|
||
|
||
import { Children, type ComponentProps, type ReactNode } from 'react'
|
||
import { STATUS, benchUrl, paperUrl, type Bench } from '@/lib/benchmarks'
|
||
import { ArrowLeft, ArrowRight } from '@hanzogui/lucide-icons-2'
|
||
import { Text, View, XStack, YStack } from '@hanzo/ui'
|
||
import { Display, Eyebrow as Kicker, Lede as Under, Title } from '@hanzo/ui/marketing'
|
||
import { useI18n } from '@hanzo/i18n/react'
|
||
import type { Metric } from './kit'
|
||
|
||
/**
|
||
* The drawn half of the bench kit (kit.tsx): every piece that renders a gui
|
||
* element, in a client module, so a page that composes them passes words and
|
||
* data across the boundary and never a gui prop object. kit.tsx re-exports all
|
||
* of it beside the helpers and the one table, whose columns are functions and
|
||
* so stay on the server.
|
||
*/
|
||
|
||
type TextProps = ComponentProps<typeof Text>
|
||
|
||
/**
|
||
* A prop that carries copy: a string is authored English (a `phrase` or the
|
||
* registry's prose), translated where it is drawn; a node is already the
|
||
* reader's (`<T>` from a server page).
|
||
*/
|
||
function useSay() {
|
||
const { t } = useI18n()
|
||
return (x: ReactNode) => (typeof x === 'string' ? t(x) : x)
|
||
}
|
||
|
||
/** The page's own leading: the body rung the rest of the site reads in. */
|
||
const LEAD = '$3'
|
||
/** The two faces, as the pages name them. */
|
||
const MONO = '$mono'
|
||
/**
|
||
* The board is monochrome. Hanzo's rows, points and figures are in the
|
||
* brightest ink the theme has (white on dark, near-black on light); everyone
|
||
* else is the theme's mid grey; a loss is marked by a glyph and a word, never a
|
||
* colour. Both inks are the design system's, so they hold body-text contrast in
|
||
* either scheme.
|
||
*/
|
||
export const OURS = 'var(--text-primary)'
|
||
export const THEM = 'var(--muted-foreground)'
|
||
/** A deficit reads in the same ink as everything else; the glyph and the word say it is one. */
|
||
export const BEHIND = 'var(--text-primary)'
|
||
/** Lining figures, so a column of numbers stays true. */
|
||
const FIGURES = { fontVariantNumeric: 'tabular-nums' } as const
|
||
|
||
/**
|
||
* The benchmark pages' ground, in the visitor's scheme like every other page:
|
||
* the design system's background and ink, so type and ground always come from
|
||
* the same theme. (A black ground forced under a light scheme left the headings
|
||
* following the visitor: dark on black. The design system decides the scheme
|
||
* at the root only, so a page cannot hold a dark island inside a light one.)
|
||
*/
|
||
export function Ground({ children }: { children: ReactNode }) {
|
||
return (
|
||
<YStack display="block" minH="100vh" bg="var(--background)" $platform-web={{ color: 'var(--text-primary)' }}>
|
||
{children}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** The page: a measure, its inset, and the black ground the figures are read on. */
|
||
export function Page({ children }: { children: ReactNode }) {
|
||
return (
|
||
<YStack render="main" maxW={1080} width="100%" mx="auto" pt={64} px={24} pb={96} bg="var(--background)" $platform-web={{ color: 'var(--text-primary)' }}>
|
||
{children}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** The small caps over a page's title. */
|
||
export function Eyebrow({ children }: { children: ReactNode }) {
|
||
return (
|
||
<Text display="block" fontSize="$1" lineHeight={LEAD} letterSpacing={0.88} textTransform="uppercase" color="var(--muted-foreground)">
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** The page heading, in the product pages' display ramp (32 / 40 / 52). */
|
||
export function H1({ children }: { children: ReactNode }) {
|
||
return (
|
||
<Display mt={8} mb={16} $platform-web={{ textWrap: 'balance' }}>
|
||
{children}
|
||
</Display>
|
||
)
|
||
}
|
||
|
||
export function Lede({ children }: { children: ReactNode }) {
|
||
return (
|
||
<Under maxW={720}>
|
||
{children}
|
||
</Under>
|
||
)
|
||
}
|
||
|
||
/** A section heading, the product pages' section role one rung down: under a chapter, never louder than it. */
|
||
export function H2({ id, children }: { id?: string; children: ReactNode }) {
|
||
return (
|
||
<Title quiet render="h2" id={id} mt={56} mb={8}>
|
||
{children}
|
||
</Title>
|
||
)
|
||
}
|
||
|
||
export function H3({ mt = 28, mb = 6, children }: { mt?: number; mb?: number; children: ReactNode }) {
|
||
return (
|
||
<Text render="h3" display="block" mt={mt} mb={mb} fontSize="$3" lineHeight={LEAD} fontFamily={MONO} fontWeight="500" color="var(--text-primary)">
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** Body copy: a measure wide, on a 1.55 leading. A `span` where it sits inside a panel. */
|
||
export function P({ render = 'p', children, ...rest }: { render?: 'p' | 'span' } & TextProps) {
|
||
return (
|
||
<Text render={render} display="block" maxW={720} lineHeight="1.3563rem" color="var(--text-secondary)" {...rest}>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** A quiet span: an annotation inside a cell or a sentence. */
|
||
export function Dim({ children }: { children: ReactNode }) {
|
||
return <Text color="var(--muted-foreground)">{children}</Text>
|
||
}
|
||
|
||
/** The line that says what a table measured or where a figure came from. */
|
||
export function Meta({ children, ...rest }: TextProps) {
|
||
return (
|
||
<Text render="p" display="block" mb={10} fontFamily={MONO} fontSize="$1" lineHeight={LEAD} color="var(--muted-foreground)" {...rest}>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** The same line under a table. */
|
||
export function Foot({ children }: { children: ReactNode }) {
|
||
return (
|
||
<Meta mt={8}>
|
||
{children}
|
||
</Meta>
|
||
)
|
||
}
|
||
|
||
/** A caveat, ruled off at the left. */
|
||
export function Note({ children }: { children: ReactNode }) {
|
||
return (
|
||
<P mt={14} fontSize="$2" lineHeight="1.2594rem" borderLeftWidth={2} borderColor="$edge" pl={14}>
|
||
{children}
|
||
</P>
|
||
)
|
||
}
|
||
|
||
/* ── the index's frame ───────────────────────────────────────────────────── */
|
||
|
||
/**
|
||
* A section of the board is a slide: it fills the screen, its content sits at
|
||
* the middle, and the page settles on its top edge. A long section grows past
|
||
* the screen as it needs to.
|
||
*/
|
||
export function Slide({ id, children }: { id: string; children: ReactNode }) {
|
||
return (
|
||
<YStack render="section" id={id} minH="100svh" justify="center" minW={0} $platform-web={{ scrollSnapAlign: 'start' }}>
|
||
{children}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** Where an anchor stops: clear of the header and the sticky chapter nav under it. */
|
||
const CLEAR = 'calc(var(--header-height, 4rem) + 56px)'
|
||
|
||
/**
|
||
* A chapter of the index's story: its name in small caps, its heading in the
|
||
* product pages' section role, and what it shows. Natural height and room
|
||
* around it; the sticky section nav's rule is the only one. The anchor sits
|
||
* clear of that nav.
|
||
*/
|
||
export function Chapter({ id, eyebrow, title, lede, children }: { id: string; eyebrow: ReactNode; title: ReactNode; lede?: ReactNode; children: ReactNode }) {
|
||
const say = useSay()
|
||
return (
|
||
<YStack render="section" id={id} aria-labelledby={`${id}-title`} minW={0} pt={96} $platform-web={{ scrollMarginTop: CLEAR }}>
|
||
<Kicker>{say(eyebrow)}</Kicker>
|
||
<Title id={`${id}-title`} mt={8} mb={12} $platform-web={{ textWrap: 'balance' }}>
|
||
{say(title)}
|
||
</Title>
|
||
{lede ? (
|
||
<Under maxW={720}>
|
||
{say(lede)}
|
||
</Under>
|
||
) : null}
|
||
{children}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** A part of a chapter: an anchor and its contents, at the height they need. */
|
||
export function Part({ id, children }: { id: string; children: ReactNode }) {
|
||
return (
|
||
<YStack render="section" id={id} minW={0} $platform-web={{ scrollMarginTop: CLEAR }}>
|
||
{children}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** The index's opening: its words over a glow of the accent, and `foot` under them. */
|
||
export function Stage({ foot, children }: { foot: ReactNode; children: ReactNode }) {
|
||
return (
|
||
<YStack render="section" position="relative" overflow="hidden" pb="$5">
|
||
<View
|
||
aria-hidden
|
||
position="absolute"
|
||
t={0}
|
||
l="50%"
|
||
x="-50%"
|
||
width={800}
|
||
height={420}
|
||
backgroundImage="radial-gradient(circle, color-mix(in srgb, var(--text-primary) 12%, transparent) 0%, transparent 70%)"
|
||
filter="blur(80px)"
|
||
pointerEvents="none"
|
||
/>
|
||
<YStack position="relative" z={1}>
|
||
{children}
|
||
<YStack mt="$6">{foot}</YStack>
|
||
</YStack>
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** The pill over the index's title: a live pip and its words. */
|
||
export function Live({ children }: { children: ReactNode }) {
|
||
return (
|
||
<Text
|
||
display="inline-flex"
|
||
items="center"
|
||
gap="$2"
|
||
pt="$1"
|
||
pb="$1"
|
||
pl="$2.5"
|
||
pr="$3"
|
||
rounded="$10"
|
||
borderWidth={1}
|
||
borderColor="$edge"
|
||
bg="var(--muted)"
|
||
fontSize="$1"
|
||
letterSpacing={0.88}
|
||
textTransform="uppercase"
|
||
color="var(--text-secondary)"
|
||
>
|
||
<View render="span" width={6} height={6} rounded="$10" bg="var(--text-primary)" />
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** A link inside a panel's note, in the note's own ink. */
|
||
export function Inline({ href, children }: { href: string; children: ReactNode }) {
|
||
return (
|
||
<Text render={<a href={href} />} color="inherit">
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** What a table row says under its label: its data, its links, a line in words. It breaks anywhere, so a long path never widens the table. */
|
||
export function Gloss({ children }: { children: ReactNode }) {
|
||
return (
|
||
<Text render="span" display="block" mt={2} fontSize="$1" lineHeight={LEAD} fontWeight="400" color="var(--muted-foreground)" $platform-web={{ overflowWrap: 'anywhere' }}>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** A block in a slide's column that may narrow below its content, so a wide table scrolls in its box instead of widening the page. An anchored one stops clear of the nav. */
|
||
export function Block({ id, children }: { id?: string; children: ReactNode }) {
|
||
return (
|
||
<View id={id} display="block" minW={0} $platform-web={id ? { scrollMarginTop: CLEAR } : undefined}>
|
||
{children}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
/** A disclosure: its handle in the meta line's type, the rest folded under it. */
|
||
export function Fold({ label, children }: { label: ReactNode; children: ReactNode }) {
|
||
const say = useSay()
|
||
return (
|
||
<View render="details" display="block" minW={0}>
|
||
<Text render="summary" display="block" mt="$4" fontFamily={MONO} fontSize="$1" lineHeight="1.35rem" color="var(--muted-foreground)" cursor="pointer">
|
||
{say(label)}
|
||
</Text>
|
||
{children}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
/* ── tables ──────────────────────────────────────────────────────────────── */
|
||
|
||
/**
|
||
* The table's parts. Real <table> elements, so the columns line up as a table
|
||
* does; each cell is a gui Text, stated a table cell, because a gui Text is an
|
||
* inline box until told otherwise.
|
||
*/
|
||
const CELL = { '$platform-web': { display: 'table-cell', ...FIGURES } } as const
|
||
|
||
/** A table that scrolls sideways inside a hairline box rather than pushing the page. */
|
||
export function Table({ children }: { children: ReactNode }) {
|
||
return (
|
||
<View overflowX="auto" borderWidth={1} borderColor="$edge" rounded={6} bg="var(--card)">
|
||
<Text render="table" width="100%" fontSize="$2" lineHeight={LEAD} color="var(--text-primary)" $platform-web={{ display: 'table', borderCollapse: 'collapse', ...FIGURES }}>
|
||
{children}
|
||
</Text>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
/** A column head: right-aligned figures unless `left`; `group` spans the heads it names. */
|
||
export function Th({ left, group, colSpan, children }: { left?: boolean; group?: boolean; colSpan?: number; children?: ReactNode }) {
|
||
return (
|
||
<Text
|
||
render={<th colSpan={colSpan} />}
|
||
{...CELL}
|
||
text={group ? 'center' : left ? 'left' : 'right'}
|
||
pt={8}
|
||
pb={group ? 2 : 8}
|
||
px={12}
|
||
borderBottomWidth={group ? 0 : 1}
|
||
borderColor="$edge"
|
||
fontWeight="500"
|
||
whiteSpace="nowrap"
|
||
color="var(--muted-foreground)"
|
||
>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** The ink a cell reads in: the row's best, the best when it is ours, the rest a step down. */
|
||
const TONE = { plain: 'inherit', dim: 'var(--muted-foreground)', best: 'var(--text-primary)', ours: OURS, rest: THEM } as const
|
||
|
||
/**
|
||
* A cell. `left` for the label column; `wrap` lets a label or a cell break on
|
||
* a phone (a figure has no space to break at, so only words wrap); `block`
|
||
* titles the rows under it.
|
||
*/
|
||
export function Td({
|
||
left,
|
||
wrap,
|
||
block,
|
||
tone = 'plain',
|
||
colSpan,
|
||
children,
|
||
}: {
|
||
left?: boolean
|
||
wrap?: boolean
|
||
block?: boolean
|
||
tone?: keyof typeof TONE
|
||
colSpan?: number
|
||
children?: ReactNode
|
||
}) {
|
||
return (
|
||
<Text
|
||
render={<td colSpan={colSpan} />}
|
||
{...CELL}
|
||
text={left || block ? 'left' : 'right'}
|
||
pt={block ? 16 : 7}
|
||
pb={7}
|
||
px={12}
|
||
minW={left && wrap ? 'calc(10em)' : undefined}
|
||
borderBottomWidth={1}
|
||
borderColor="$edge"
|
||
whiteSpace={wrap || block ? 'normal' : 'nowrap'}
|
||
fontSize={block ? '$1' : undefined}
|
||
fontWeight={tone === 'best' || tone === 'ours' ? '600' : undefined}
|
||
color={block ? 'var(--muted-foreground)' : TONE[tone]}
|
||
>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** A row; `dim` reads the whole row a step down. */
|
||
export function Tr({ dim, children }: { dim?: boolean; children: ReactNode }) {
|
||
return (
|
||
<Text render="tr" color={dim ? 'var(--muted-foreground)' : 'inherit'} $platform-web={{ display: 'table-row' }}>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** A metric with its bootstrap interval, where the run recorded one. */
|
||
export function Cell({ m }: { m: Metric | undefined }) {
|
||
if (m == null) return <>–</>
|
||
if (typeof m === 'number') return <>{(m * 100).toFixed(1)}</>
|
||
return (
|
||
<>
|
||
{(m.mean * 100).toFixed(1)}
|
||
<Text ml={6} fontSize="$1" color="var(--muted-foreground)">
|
||
[{(m.lo * 100).toFixed(1)}, {(m.hi * 100).toFixed(1)}]
|
||
</Text>
|
||
</>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* The page frame: where you are, what this is, and the one sentence.
|
||
*
|
||
* The breadcrumb is a real link rather than a decoration — it is the only path
|
||
* back to the other benchmarks from a page somebody landed on from search,
|
||
* which is how most readers will arrive at any of these.
|
||
*/
|
||
export function Shell({ b, children }: { b: Bench; children: ReactNode }) {
|
||
const { t } = useI18n()
|
||
return (
|
||
<Page>
|
||
<Text render="p" display="block" mb={4} fontSize="$1" lineHeight={LEAD} color="var(--muted-foreground)">
|
||
<a href="/benchmarks">{t('Benchmarks')}</a> / {b.name}
|
||
</Text>
|
||
<Eyebrow>{t(STATUS[b.status])}</Eyebrow>
|
||
<H1>{t(b.title)}</H1>
|
||
<Lede>{t(b.lede)}</Lede>
|
||
{children}
|
||
</Page>
|
||
)
|
||
}
|
||
|
||
/** One fact of the spec: its key in the narrow column, its value beside it. */
|
||
function Fact({ term, children }: { term: string; children: ReactNode }) {
|
||
return (
|
||
<>
|
||
<Text render="dt" color="var(--muted-foreground)">
|
||
{term}
|
||
</Text>
|
||
<Text render="dd" m={0} color="inherit">
|
||
{children}
|
||
</Text>
|
||
</>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Whose data it is, on whose terms, and the command that rebuilds the tables.
|
||
*
|
||
* The licence line is the reason this block exists rather than a paragraph:
|
||
* three of these datasets are non-commercial and one is unreleased, and a
|
||
* reader about to fetch one should not have to find that out from a footnote.
|
||
*/
|
||
export function Spec({ b }: { b: Bench }) {
|
||
const { t } = useI18n()
|
||
const d = b.dataset
|
||
return (
|
||
<>
|
||
<Text
|
||
render="dl"
|
||
display="grid"
|
||
gridTemplateColumns="minmax(120px, max-content) 1fr"
|
||
rowGap={8}
|
||
columnGap={20}
|
||
m={0}
|
||
mt={20}
|
||
maxW={760}
|
||
fontSize="$2"
|
||
lineHeight={LEAD}
|
||
color="var(--text-primary)"
|
||
>
|
||
{d ? (
|
||
<>
|
||
<Fact term={t('dataset')}>
|
||
<a href={d.url}>{d.name}</a>
|
||
</Fact>
|
||
<Fact term={t('licence')}>{d.license}</Fact>
|
||
<Fact term={t('introduced by')}>
|
||
<a href={d.paper.url}>{d.paper.title}</a> — {d.paper.authors}, {d.paper.venue}
|
||
</Fact>
|
||
</>
|
||
) : null}
|
||
<Fact term={t('harness')}>
|
||
<a href={b.harness.url}>{b.harness.repo}</a>
|
||
</Fact>
|
||
{b.papers.length ? (
|
||
<Fact term={b.papers.length > 1 ? t('our papers') : t('our paper')}>
|
||
{b.papers.map((p, i) => (
|
||
<Text key={p.slug} color="inherit">
|
||
{i ? ', ' : ''}
|
||
<a href={paperUrl(p)}>{p.title}</a>
|
||
</Text>
|
||
))}
|
||
</Fact>
|
||
) : null}
|
||
</Text>
|
||
{b.harness.command === '—' ? null : (
|
||
<>
|
||
<Text
|
||
render="code"
|
||
display="block"
|
||
mt={10}
|
||
py={10}
|
||
px={14}
|
||
borderWidth={1}
|
||
borderColor="$edge"
|
||
rounded={6}
|
||
overflowX="auto"
|
||
bg="var(--card)"
|
||
fontFamily={MONO}
|
||
fontSize="$1"
|
||
color="var(--text-primary)"
|
||
>
|
||
{b.harness.command}
|
||
</Text>
|
||
<Foot>
|
||
{b.quoted
|
||
? t('prints the table on this page — transcribed from {where}, {when}', { where: b.quoted.where, when: b.quoted.when })
|
||
: t('regenerates every table on this page from the raw runs')}
|
||
</Foot>
|
||
</>
|
||
)}
|
||
</>
|
||
)
|
||
}
|
||
|
||
/** A row of links set as buttons. */
|
||
export function Actions({ center, children }: { center?: boolean; children: ReactNode }) {
|
||
return (
|
||
<XStack flexWrap="wrap" gap={12} mt={center ? 8 : 26} justify={center ? 'center' : undefined}>
|
||
{children}
|
||
</XStack>
|
||
)
|
||
}
|
||
|
||
/** A link set as a button: outlined, or `solid` for the one to take. */
|
||
export function Go({ href, solid, children }: { href: string; solid?: boolean; children: ReactNode }) {
|
||
return (
|
||
<Text
|
||
render={<a href={href} />}
|
||
display="inline-flex"
|
||
items="center"
|
||
gap={8}
|
||
py={12}
|
||
px={24}
|
||
rounded={9999}
|
||
borderWidth={1}
|
||
borderColor={solid ? 'transparent' : 'var(--border-strong)'}
|
||
bg={solid ? 'var(--text-primary)' : 'transparent'}
|
||
fontSize="$3"
|
||
lineHeight={LEAD}
|
||
fontWeight={solid ? '600' : '500'}
|
||
color={solid ? 'var(--background)' : 'var(--text-primary)'}
|
||
>
|
||
{children}
|
||
</Text>
|
||
)
|
||
}
|
||
|
||
/** Where to run what these pages measure. An address, not a claim. */
|
||
export function Cloud() {
|
||
const { t } = useI18n()
|
||
return (
|
||
<YStack
|
||
mt={56}
|
||
mb={24}
|
||
py={36}
|
||
px={32}
|
||
rounded={12}
|
||
borderWidth={1}
|
||
borderColor="var(--border-strong)"
|
||
backgroundImage="linear-gradient(180deg, var(--muted) 0%, var(--card) 100%)"
|
||
items="center"
|
||
gap={16}
|
||
$platform-web={{ textAlign: 'center' }}
|
||
>
|
||
<Meta m={0} letterSpacing={1.32} textTransform="uppercase">
|
||
Hanzo AI Cloud
|
||
</Meta>
|
||
<Text render="h3" display="block" m={0} fontSize="$7" lineHeight="1.6406rem" fontWeight="600" letterSpacing="$7" color="var(--text-primary)" $md={{ fontSize: '$10', lineHeight: '2.5rem' }}>
|
||
{t('Build on Hanzo Cloud')}
|
||
</Text>
|
||
<Actions center>
|
||
<Go href="https://cloud.hanzo.ai" solid>
|
||
{t('Start on Hanzo Cloud')}
|
||
<ArrowRight size={14} color="var(--background)" shrink={0} aria-hidden data-i18n-mirror />
|
||
</Go>
|
||
<Go href="/pricing">{t('View Models & Pricing')}</Go>
|
||
</Actions>
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** One end of the Prev / Next bar. */
|
||
function Step({ href, label, name, end }: { href: string; label: string; name: string; end?: boolean }) {
|
||
return (
|
||
<YStack render={<a href={href} />} gap={2} items={end ? 'flex-end' : 'flex-start'}>
|
||
<XStack items="center" gap={4}>
|
||
{end ? null : <ArrowLeft size={12} color="var(--muted-foreground)" shrink={0} aria-hidden data-i18n-mirror />}
|
||
<Text fontSize="$1" lineHeight={LEAD} textTransform="uppercase" letterSpacing={0.44} color="var(--muted-foreground)">
|
||
{label}
|
||
</Text>
|
||
{end ? <ArrowRight size={12} color="var(--muted-foreground)" shrink={0} aria-hidden data-i18n-mirror /> : null}
|
||
</XStack>
|
||
<Text fontSize="$3" lineHeight={LEAD} fontWeight="500" color="var(--text-primary)">
|
||
{name}
|
||
</Text>
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** The other benchmarks, at the foot of each one. Provides Prev/Next and Related navigation. */
|
||
export function More({ b, all }: { b: Bench; all: Bench[] }) {
|
||
const { t } = useI18n()
|
||
const idx = all.findIndex((x) => x.slug === b.slug)
|
||
const prev = idx > 0 ? all[idx - 1] : all[all.length - 1]
|
||
const next = idx >= 0 && idx < all.length - 1 ? all[idx + 1] : all[0]
|
||
const sameLane = all.filter((x) => x.lane === b.lane && x.slug !== b.slug)
|
||
const rest = all.filter((x) => x.slug !== b.slug && x.lane !== b.lane)
|
||
|
||
return (
|
||
<YStack mt={48} pt={16}>
|
||
<Cloud />
|
||
<YStack borderTopWidth={1} borderColor="$edge" pt={32}>
|
||
{/* Prev / Next bar */}
|
||
<XStack
|
||
flexWrap="wrap"
|
||
justify="space-between"
|
||
items="center"
|
||
gap={16}
|
||
mb={32}
|
||
py={16}
|
||
px={20}
|
||
bg="var(--card)"
|
||
borderWidth={1}
|
||
borderColor="var(--border)"
|
||
rounded={8}
|
||
>
|
||
{prev ? <Step href={benchUrl(prev.slug)} label={t('Previous')} name={prev.name} /> : <View />}
|
||
<Text
|
||
render={<a href="/benchmarks" />}
|
||
py={6}
|
||
px={12}
|
||
rounded={4}
|
||
bg="var(--edge)"
|
||
borderWidth={1}
|
||
borderColor="var(--border)"
|
||
fontSize="$2"
|
||
lineHeight={LEAD}
|
||
color="var(--muted-foreground)"
|
||
>
|
||
{t('All Benchmarks Board')}
|
||
</Text>
|
||
{next ? <Step href={benchUrl(next.slug)} label={t('Next')} name={next.name} end /> : <View />}
|
||
</XStack>
|
||
|
||
{/* Related benchmarks in same lane */}
|
||
{sameLane.length > 0 && (
|
||
<YStack mb={24}>
|
||
<H3 mt={0} mb={12}>
|
||
{t('Related in {lane}', { lane: b.lane })}
|
||
</H3>
|
||
<XStack flexWrap="wrap" gap={10}>
|
||
{sameLane.map((x) => (
|
||
<Text
|
||
key={x.slug}
|
||
render={<a href={benchUrl(x.slug)} />}
|
||
display="inline-flex"
|
||
items="center"
|
||
gap={6}
|
||
py={6}
|
||
px={12}
|
||
rounded={6}
|
||
bg="var(--edge)"
|
||
borderWidth={1}
|
||
borderColor="var(--border)"
|
||
fontSize="$2"
|
||
lineHeight={LEAD}
|
||
color="var(--text-primary)"
|
||
>
|
||
<Text color="inherit">{x.name}</Text>
|
||
<ArrowRight size={12} color="var(--muted-foreground)" shrink={0} aria-hidden data-i18n-mirror />
|
||
</Text>
|
||
))}
|
||
</XStack>
|
||
</YStack>
|
||
)}
|
||
|
||
{/* All other benchmarks */}
|
||
<YStack>
|
||
<Text display="block" mb={8} fontFamily={MONO} fontSize="$2" lineHeight={LEAD} textTransform="uppercase" letterSpacing={0.52} color="var(--muted-foreground)">
|
||
{t('Other benchmarks')}
|
||
</Text>
|
||
<P m={0} fontSize="$2">
|
||
{rest.map((x, i) => (
|
||
<Text key={x.slug} color="inherit">
|
||
{i ? ' · ' : ''}
|
||
<Text render={<a href={benchUrl(x.slug)} />} color="var(--text-secondary)">
|
||
{x.name}
|
||
</Text>
|
||
</Text>
|
||
))}
|
||
</P>
|
||
</YStack>
|
||
</YStack>
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ *
|
||
* The summary furniture: a figure, a comparison, and the box they sit in.
|
||
*
|
||
* /benchmarks opens on a summary, and a summary is where a benchmark page
|
||
* usually starts lying — a big green number with no split, no baseline and no
|
||
* scale under it. So these three take the same arguments the tables do. A Tile
|
||
* carries the measurement it came from and links its page. A Bar is drawn on
|
||
* the scale its whole group shares, so a 2× gap looks like a 2× gap and a
|
||
* rounding error looks like one. A null value renders as an absent bar labelled
|
||
* `–`, never as zero.
|
||
* ------------------------------------------------------------------ */
|
||
|
||
/** A section's one number: set large, what it measures, where it came from, and the page that draws it in full. */
|
||
export function Stat({ figure, label, note, href }: { figure: ReactNode; label: ReactNode; note?: ReactNode; href: string }) {
|
||
const say = useSay()
|
||
return (
|
||
<YStack render={<a href={href} />} mt={24} mb={8} gap={4} maxW={720}>
|
||
<Text display="block" fontFamily={MONO} fontSize="$11" lineHeight="$11" color={OURS} $platform-web={FIGURES}>
|
||
{figure}
|
||
</Text>
|
||
<Text display="block" fontSize="$3" lineHeight={LEAD} color="var(--text-primary)">
|
||
{say(label)}
|
||
</Text>
|
||
{note ? (
|
||
<Text display="block" fontSize="$2" lineHeight={LEAD} color="var(--muted-foreground)">
|
||
{say(note)}
|
||
</Text>
|
||
) : null}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** Measured rates as bars on one scale: the label, a bar drawn from the value, and the reading as it was recorded. */
|
||
export function Rates({ rows }: { rows: { label: string; value: number; shown: string }[] }) {
|
||
const top = Math.max(...rows.map((r) => r.value), 1)
|
||
return (
|
||
<YStack gap={10} role="list">
|
||
{rows.map((r) => (
|
||
<YStack key={r.label} role="listitem" gap={4}>
|
||
<XStack justify="space-between" gap={12} flexWrap="wrap">
|
||
<Text fontSize="$2" lineHeight={LEAD} color="var(--text-primary)">
|
||
{r.label}
|
||
</Text>
|
||
<Text fontFamily={MONO} fontSize="$2" lineHeight={LEAD} color={OURS} $platform-web={FIGURES}>
|
||
{r.shown}
|
||
</Text>
|
||
</XStack>
|
||
<View height={8} rounded={2} bg="var(--edge)" overflow="hidden">
|
||
<View width={`${Math.min(100, (r.value / top) * 100)}%`} height="100%" bg={OURS} transition="quick" />
|
||
</View>
|
||
</YStack>
|
||
))}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** Where a section's result stops: each limit in a sentence, its evidence, and the row or page that shows it. */
|
||
export function Limits({ items, children }: { items: { title: string; detail: string; href: string }[]; children?: ReactNode }) {
|
||
const { t } = useI18n()
|
||
if (!items.length && !children) return null
|
||
return (
|
||
<YStack mt={40} pt={20} borderTopWidth={1} borderColor="var(--border)" gap={4}>
|
||
<Text render="h3" display="block" mb={8} fontSize="$5" lineHeight={LEAD} fontWeight="500" color="var(--text-primary)">
|
||
{t('Where it stops')}
|
||
</Text>
|
||
{items.map((b) => (
|
||
<Text key={b.title} render="p" display="block" maxW={760} mb={10} fontSize="$3" lineHeight="1.55rem" color="var(--text-secondary)">
|
||
<Text fontWeight="600" color="var(--text-primary)">
|
||
{t(b.title)}
|
||
</Text>{' '}
|
||
{t(b.detail)}{' '}
|
||
<Inline href={b.href}>{t('The evidence')}</Inline>
|
||
</Text>
|
||
))}
|
||
{children}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** One measured figure. `label` is what was measured; `note` is where it came from; `href` is its page. */
|
||
export function Tile({ figure, unit, label, note, href }: { figure: string; unit?: string; label: ReactNode; note?: ReactNode; href: string }) {
|
||
const say = useSay()
|
||
return (
|
||
<YStack render={<a href={href} />} borderWidth={1} borderColor="var(--border)" rounded={8} py={14} px={16} bg="var(--card)">
|
||
<Text display="block" fontSize="$7" lineHeight="1.4438rem" fontFamily={MONO} color={OURS} $platform-web={FIGURES}>
|
||
{figure}
|
||
{unit ? (
|
||
<Text ml={4} fontSize="$2" color="var(--muted-foreground)">
|
||
{unit}
|
||
</Text>
|
||
) : null}
|
||
</Text>
|
||
<Text display="block" mt={6} fontSize="$2" lineHeight={LEAD} color="var(--text-primary)">
|
||
{say(label)}
|
||
</Text>
|
||
{note ? (
|
||
<Text display="block" mt={2} fontSize="$1" lineHeight={LEAD} color="var(--muted-foreground)">
|
||
{say(note)}
|
||
</Text>
|
||
) : null}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* A row in a comparison: what it is, what it scored, and whether it is ours.
|
||
* `label` is authored English, drawn through `t` with `values` filling its
|
||
* `{name}` arguments.
|
||
*/
|
||
export type Span = { label: string; values?: Record<string, string | number>; value: number | null; ours?: boolean }
|
||
|
||
/**
|
||
* A group of bars on ONE scale: the largest value present, so a caller cannot
|
||
* flatter a gap by picking a small maximum.
|
||
*/
|
||
export function Bars({ spans }: { spans: Span[] }) {
|
||
const { t } = useI18n()
|
||
const top = Math.max(...spans.map((s) => s.value ?? 0), 0.0001)
|
||
return (
|
||
<YStack>
|
||
{spans.map((s) => (
|
||
<View key={[s.label, ...Object.values(s.values ?? {})].join(' ')} display="grid" gridTemplateColumns="minmax(96px, 34%) 1fr max-content" items="center" gap={10} mb={7}>
|
||
<Text fontSize="$2" lineHeight={LEAD} color="var(--text-secondary)">
|
||
{t(s.label, s.values)}
|
||
</Text>
|
||
<View height={12} rounded={3} bg="var(--edge)" overflow="hidden">
|
||
<View width={`${Math.min(100, ((s.value ?? 0) / top) * 100)}%`} height="100%" bg={s.ours ? OURS : THEM} transition="quick" />
|
||
</View>
|
||
<Text minW={44} text="right" fontFamily={MONO} fontSize="$2" lineHeight={LEAD} color={s.ours ? OURS : THEM} $platform-web={FIGURES}>
|
||
{s.value == null ? '–' : s.value.toFixed(1)}
|
||
</Text>
|
||
</View>
|
||
))}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/** A titled box around one comparison, with the protocol under it and its title linking its page. */
|
||
export function Panel({ id, title, note, href, children }: { id?: string; title: ReactNode; note?: ReactNode; href: string; children?: ReactNode }) {
|
||
const say = useSay()
|
||
return (
|
||
<YStack id={id} borderWidth={1} borderColor="var(--border)" rounded={8} py={16} px={18} bg="var(--card)">
|
||
<Text render="p" display="block" mb={12} fontSize="$2" lineHeight={LEAD} fontWeight="500" color="var(--text-primary)">
|
||
<Text render={<a href={href} />} display="inline-flex" items="center" gap={6} color="var(--text-primary)">
|
||
{say(title)}
|
||
<ArrowRight size={14} color="var(--muted-foreground)" shrink={0} aria-hidden data-i18n-mirror />
|
||
</Text>
|
||
</Text>
|
||
{children}
|
||
{note ? (
|
||
<Text render="p" display="block" mt={12} mb={0} fontSize="$1" color="var(--muted-foreground)" $platform-web={{ lineHeight: "1.45" }}>
|
||
{say(note)}
|
||
</Text>
|
||
) : null}
|
||
</YStack>
|
||
)
|
||
}
|
||
|
||
/*
|
||
* A grid of whole rows. A ragged last row reads as a missing result, so Even
|
||
* lays n items in three columns when three divides n and in two when two does:
|
||
* six go three by two on a wide screen and two by three on a tablet, and every
|
||
* count goes one column on a phone. A count neither divides (five, seven) keeps
|
||
* three columns with its last row centred. Each item's share is a gui
|
||
* breakpoint prop on the cell that holds it.
|
||
*/
|
||
const HALF = 'calc((100% - 16px) / 2 - 0.5px)'
|
||
const THIRD = 'calc((100% - 32px) / 3 - 0.5px)'
|
||
|
||
/** Items in whole rows of three or two, one column on a phone. */
|
||
export function Even({ children }: { children: ReactNode }) {
|
||
const n = Children.count(children)
|
||
const by3 = n % 3 === 0 ? true : n % 2 === 0 ? false : n > 1
|
||
const by2 = n % 2 === 0
|
||
return (
|
||
<XStack flexWrap="wrap" justify="center" items="stretch" gap={16}>
|
||
{Children.map(children, (child) => (
|
||
<YStack
|
||
flexBasis="100%"
|
||
grow={0}
|
||
shrink={0}
|
||
minW={0}
|
||
$sm={by2 ? { flexBasis: HALF } : undefined}
|
||
$lg={by3 ? { flexBasis: THIRD } : undefined}
|
||
>
|
||
{child}
|
||
</YStack>
|
||
))}
|
||
</XStack>
|
||
)
|
||
}
|
||
|
||
/** The lead of a list for a summary: balanced rows, avoiding single orphan items. */
|
||
export const lead = <T,>(xs: T[]): T[] => {
|
||
const n = Math.min(xs.length, 6)
|
||
if (n === 4) return xs.slice(0, 4)
|
||
return xs.slice(0, n >= 3 ? n - (n % 3) : n)
|
||
}
|
||
|
||
/** Anything that should sit side by side on a wide screen and stack on a phone. */
|
||
export function Row({ children }: { children: ReactNode }) {
|
||
return (
|
||
<View display="grid" gridTemplateColumns="repeat(auto-fit, minmax(260px, 1fr))" gap={16}>
|
||
{children}
|
||
</View>
|
||
)
|
||
}
|