Files
hanzo.ai/components/product/ProductArticlesSection.tsx
zeekayandGitHub 3df66c3990 i18n: /m* through /r* and the product kits draw their words through t (#70)
Pages from /machines to /risk, the model pages, and the kits they are made of
(page-kit, ProductLanding, LayerPage, Stack, the models kit, band's Head) mark
authored copy where it is written and draw it in the reader's language. A server
page marks a literal phrase('…') or wraps it in <T>; a client page calls t. The
kits translate a string prop through useSay (components/marketing/say), so a
page passes phrase('…') and markup passes as written. An arrow that points
turns over right to left (data-i18n-mirror).

Rebased on main: the fold-based heroes (Fold, PageHero, MachinesHero, the
product and layer pages) keep main's structure and copy, with their label,
title and lede marked phrase. /platform is main's CloudLanding; the deleted
/platform/layers and the old referral components stay deleted.

Claude-Session: https://claude.ai/code/session_015XYKLjZtDTJEnMo4eK3AFH
2026-10-30 00:18:44 -07:00

118 lines
3.7 KiB
TypeScript

'use client'
import { ExternalLink, BookOpen, ArrowRight } from 'lucide-react'
import { Box, Text, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Chip, Eyebrow, Lede, More, Title } from '@hanzo/ui/marketing'
import { Band, Card, Head } from '@/components/marketing/band'
import { Rise } from '@/components/marketing/rise'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
import { useSay } from '@/components/marketing/say'
export interface ArticleItem {
title: string
href: string
date?: string
category: string
description: string
external?: boolean
}
export interface ProductArticlesSectionProps {
id?: string
eyebrow?: string
title?: string
lede?: string
articles: ArticleItem[]
moreHref?: string
moreLabel?: string
}
export function ProductArticlesSection({
id = 'articles',
eyebrow = phrase('Research & Engineering'),
title = phrase('Deep-dives, benchmarks, and architectural specifications.'),
lede = phrase('Research, benchmarks and engineering posts from the Hanzo team.'),
articles,
moreHref = 'https://hanzo.blog',
moreLabel = phrase('More on the Hanzo blog →'),
}: ProductArticlesSectionProps) {
const { t } = useI18n()
const say = useSay()
return (
<Band id={id} measure={1180}>
<Head
eyebrow={eyebrow}
title={title}
lede={lede}
loud
/>
<Grid columns={{ min: 300, max: 3 }} gap={16} mt="$6">
{articles.map((article, i) => (
<Rise key={article.href} beat={i}>
<YStack
render={
<a
href={article.href}
target={article.href.startsWith('http') ? '_blank' : undefined}
rel={article.href.startsWith('http') ? 'noreferrer' : undefined}
/>
}
height="100%"
$platform-web={{ textDecoration: 'none', color: 'inherit' }}
>
<Card
display="flex"
flexDirection="column"
justify="space-between"
gap="$4"
p="$6"
height="100%"
borderColor="var(--border)"
bg="$panel"
hoverStyle={{ borderColor: 'var(--border-strong)' }}
>
<YStack gap="$3">
<XStack justify="space-between" items="center">
<Chip px={10} py={3} fontSize="$1" lineHeight="$1">
{say(article.category)}
</Chip>
{article.date && (
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)">
{article.date}
</Text>
)}
</XStack>
<Text render="h3" fontSize="$4" lineHeight="$4" fontWeight="600" color="$ink">
{say(article.title)}
</Text>
<Text fontSize="$2" lineHeight="$3" color="var(--text-secondary)">
{say(article.description)}
</Text>
</YStack>
<XStack items="center" gap="$1" mt="auto" pt="$3">
<Text fontSize="$2" lineHeight="$2" fontWeight="600" color="var(--primary)">
{t('Read the article')}
</Text>
<ArrowRight size={14} color="var(--primary)" data-i18n-mirror />
</XStack>
</Card>
</YStack>
</Rise>
))}
</Grid>
{moreHref && (
<XStack mt="$6" justify="center">
<More href={moreHref}>{say(moreLabel)}</More>
</XStack>
)}
</Band>
)
}