Files
zeekay ac64d5d7dd Folds say one line: every first screen keeps its heading and a single lede
The owner's spec for a landing page is the models bento's: monochrome, the
words left and the art right, no extra prose. The folds carried two to four
sentences under each heading, and some a claim, a lede and a list besides.
Each now says one line: ProductLanding the tagline where a page states one
(the lede still describes it to search), a claim alone where the claim is
the line (/payments, /treasury, /zap, the extension), and elsewhere the
sentence that says what the thing is. /philosophy's two points, /dns' claim
that repeated its title, /calculator's capabilities line and the
Spare clauses on /research and /learn go.
2026-10-30 06:42:40 -07:00

74 lines
3.2 KiB
TypeScript

'use client'
import { YStack } from '@hanzo/gui'
import { Action } from '@hanzo/ui/marketing'
import { Fold } from '@/components/marketing/fold'
import { Grid } from '@hanzo/ui/grid'
import { useI18n } from '@hanzo/i18n/react'
import { SurfaceCard } from '@/components/download/surface-card'
import { LEAD, REST } from '@/data/downloads'
/**
* Every way to get Hanzo, on one page.
*
* THE PAGE IS HERE, and it is the only copy. It stood on hanzo.app and this
* route was a shell that forwarded to it — which held only while that host
* served the page itself. hanzo.app answers with a redirect now: its root sends
* a reader here and every other path to the builder, so a forward from this
* route arrived back at /dev and the download page could not be reached in a
* browser at all. A page that two hosts point at is a page with no home.
*
* A surface appears iff a READER can act on it. The builds come from
* `data/releases.json`, which `scripts/sync-releases.mjs` resolves anonymously
* against the real releases on every build, so a platform that publishes
* nothing — or publishes only where the public cannot reach — stops rendering
* rather than becoming a button that 404s. There is deliberately no mobile
* card: no iOS or Android build has shipped to either store, and a tile that
* apologises for itself occupies the place of a product. `data/downloads.ts`
* carries both measurements.
*
* The chrome is the marketing layout's, which draws the header and footer for
* every page in this group, and the title comes from `layout.tsx` beside this
* file — so this file is the content and nothing else.
*/
export default function DownloadPage() {
const { t } = useI18n()
return (
<>
{/* The first screen: the promise over the lead surface, its builds, its install line and the agent running. */}
<Fold
label={t('Download')}
title={t('Get Hanzo everywhere you work.')}
lede={t('Install the coding agent, open the workspaces that need nothing installed, and sign in to all of them the same way.')}
actions={
<>
<Action href="#surfaces" fill>
{t('Every way to get it')}
</Action>
<Action href="https://docs.hanzo.ai/docs/quickstart">{t('The quickstart')}</Action>
</>
}
>
<YStack width="100%" maxW={1152} mx="auto">
<SurfaceCard surface={LEAD} lead />
</YStack>
</Fold>
<YStack id="surfaces" px="$4" pt="$8" pb="$10" $md={{ px: '$6', pt: '$10' }}>
{/* `width="100%"` is load-bearing on a centred column: without it
`alignSelf="center"` is shrink-to-fit and `maxWidth` caps a width the
column never takes, which reads as a section that is silently narrow. */}
<YStack self="center" width="100%" maxW={1152} gap="$8">
{/* auto-fit rather than auto-fill: the row is a fixed set of surfaces,
so a survivor stretches across the empty track instead of leaving it. */}
<Grid columns="repeat(auto-fit, minmax(min(460px, 100%), 1fr))" gap={20} items="flex-start">
{REST.map((s) => (
<SurfaceCard key={s.id} surface={s} />
))}
</Grid>
</YStack>
</YStack>
</>
)
}