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.
66 lines
2.7 KiB
TypeScript
66 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import { ArrowLeft, Home } from 'lucide-react'
|
|
import { phrase } from '@hanzo/i18n'
|
|
import { useI18n } from '@hanzo/i18n/react'
|
|
// The static export serves this as the SPA fallback for every unresolved path
|
|
// on BOTH hosts, so it wears the same shared chrome as every other page —
|
|
// otherwise a mistyped URL is where the two sites visibly become two products.
|
|
import { SiteHeader, SiteFooter } from '@/components/home/shell'
|
|
import { Box, Text, YStack } from '@hanzo/ui'
|
|
import { Action, Chip, Lede, Pair } from '@hanzo/ui/marketing'
|
|
|
|
/**
|
|
* The 404, in the landing's own words: the section label, one title, one line,
|
|
* two ways on, and the places a reader most often meant. No art and no motion:
|
|
* a clipped gradient numeral is what this page used to break on.
|
|
*/
|
|
|
|
const CHIP = { px: 16, py: 8, bg: 'var(--secondary)' } as const
|
|
|
|
const ELSEWHERE = [
|
|
{ href: '/products', label: phrase('Products') },
|
|
{ href: 'https://docs.hanzo.ai', label: phrase('Documentation') },
|
|
{ href: '/contact', label: phrase('Contact') },
|
|
{ href: 'https://status.hanzo.ai', label: phrase('Status') },
|
|
]
|
|
|
|
export default function NotFound() {
|
|
const { t } = useI18n()
|
|
return (
|
|
<Box display="flex" flexDirection="column" minH="100vh" bg="$background" $platform-web={{ color: 'var(--foreground)' }}>
|
|
<SiteHeader />
|
|
{/* The first screen under the bar is the message, centred; the footer starts below the fold. */}
|
|
<YStack render="main" minH="calc(100svh - var(--header, 64px))" items="center" justify="center" px="var(--page-gutter)" py="$12" gap={16} $platform-web={{ textAlign: 'center' }}>
|
|
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)" data-i18n-ltr>
|
|
404
|
|
</Text>
|
|
<Text render="h1" fontSize="$10" lineHeight="$10" fontWeight="600" color="var(--foreground)" $platform-web={{ letterSpacing: '-0.03em' }}>
|
|
{t('Page not found.')}
|
|
</Text>
|
|
<Lede maxW={448} mx="auto">
|
|
{t("This page doesn't exist, or it has moved.")}
|
|
</Lede>
|
|
<Pair mt={8}>
|
|
<Action href="/" fill>
|
|
<Home width={16} height={16} />
|
|
{t('Go home')}
|
|
</Action>
|
|
<Action render="button" onClick={() => window.history.back()}>
|
|
<ArrowLeft width={16} height={16} data-i18n-mirror />
|
|
{t('Go back')}
|
|
</Action>
|
|
</Pair>
|
|
<Box display="flex" flexDirection="row" flexWrap="wrap" justify="center" gap="$3" mt={32}>
|
|
{ELSEWHERE.map((link) => (
|
|
<Chip href={link.href} key={link.href} {...CHIP}>
|
|
{t(link.label)}
|
|
</Chip>
|
|
))}
|
|
</Box>
|
|
</YStack>
|
|
<SiteFooter />
|
|
</Box>
|
|
)
|
|
}
|