Files
zeekay 6936641c78 landing: the agents script is folded until asked for, and highlighted at build
Each step's lines come from lib/headless and are highlighted by the
server (components/code), so the window stands about as tall as the
install beside it and no highlighter reaches the browser.
2026-10-30 06:14:37 -07:00

124 lines
5.1 KiB
TypeScript

'use client'
import { Suspense, useEffect, type ReactNode } from 'react'
import dynamic from 'next/dynamic'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
import { XStack, YStack } from '@hanzo/ui'
import { More } from '@hanzo/ui/marketing'
import { track } from '@hanzo/app/lib/tags'
import { Box } from '@/components/ui'
import { SiteFooter, SiteHeader } from './shell'
import { Beta } from './beta'
import { Guard } from './guard'
import Start from './Start'
import Backing from './Backing'
import { Onward } from './label'
// Below the first screen, so each is prerendered into the HTML and its script
// loads at low priority, outside the first load perf.budget.json holds. The
// boundary keeps the first screen from waiting on their chunks to hydrate.
const Plans = dynamic(() => import('@/components/pricing/Plans'))
const Pareto = dynamic(() => import('./Pareto'))
const Faster = dynamic(() => import('./Faster'))
const Cheaper = dynamic(() => import('./Cheaper'))
const Compatibility = dynamic(() => import('./Compatibility'))
const Models = dynamic(() => import('./Models'))
const Products = dynamic(() => import('./Products'))
const Platform = dynamic(() => import('./Platform'))
const Trusted = dynamic(() => import('./Trusted'))
const OpenSource = dynamic(() => import('./OpenSource'))
/**
* Where a link goes, as the funnel names it: a path on this site, origin and
* path elsewhere, with its own query kept and the visitor ids another Hanzo
* host is handed (`hz_aid`, `hz_sid`, `hz_ft`, written onto the link as it is
* pressed) taken off, so a target is one value however many people press it.
*/
function target(a: HTMLAnchorElement): string {
const u = new URL(a.href)
for (const k of ['hz_aid', 'hz_sid', 'hz_ft']) u.searchParams.delete(k)
const q = u.searchParams.toString()
return `${u.origin === window.location.origin ? '' : u.origin}${u.pathname}${q ? `?${q}` : ''}${u.hash}`
}
/**
* Every link followed on the landing, as one funnel event through `track` (our
* stream always, the ad tags as consent allows): `cta_clicked { page, section,
* cta, target }`. The section is the band the link stands in (its
* `data-hz-view`, else the hero's slot); `cta` is the link's own `data-cta` or
* its words. A link marked `data-ga` sends its own moment (a plan card's
* `plan_clicked`), so it is not counted twice. Where on the page the press
* landed is @hanzo/observe's `$click`, for the heat map.
*/
function useCtas() {
useEffect(() => {
const click = (e: MouseEvent) => {
const a = (e.target as Element | null)?.closest?.('main a[href]') as HTMLAnchorElement | null
if (!a || a.hasAttribute('data-ga')) return
const section = a.closest('[data-hz-view]')?.getAttribute('data-hz-view') ?? a.closest('[data-slot]')?.getAttribute('data-slot') ?? 'page'
const cta = a.getAttribute('data-cta') ?? (a.getAttribute('aria-label') || a.textContent || '').trim().slice(0, 80)
track('cta_clicked', { page: '/', section, cta, target: target(a) })
}
document.addEventListener('click', click, { capture: true })
return () => document.removeEventListener('click', click, { capture: true })
}, [])
}
/**
* Under the plans: every rate. Its
* own component so the words are its own: Home stays a component that never
* re-renders after hydration, because an update to it would reach the
* boundary below before that boundary has hydrated, and React would throw its
* server HTML away and draw it again from nothing.
*/
function Unsure() {
const { t } = useI18n()
return (
<XStack justify="center" flexWrap="wrap" columnGap={28} rowGap={8}>
<More href="/pricing">
<Onward>{t('Every plan, model rate and tool price')}</Onward>
</More>
</XStack>
)
}
/**
* The landing at `/`: the promise and who backs us, then the apps, every
* model, and the proof: Smarter, Faster, Cheaper. Then
* what we ship in the open, the agents it works with (and how an agent signs
* itself up), the stack underneath, pricing, and who trusts us; then the
* footer. The questions that used to end the page are on /learn.
*/
export default function Home({ samples }: { samples?: readonly ReactNode[] }) {
useCtas()
return (
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)', overflowX: 'clip' }}>
<SiteHeader />
<main>
<Beta />
<Start />
<Backing />
<Suspense>
<Guard><Products /></Guard>
<Guard><Models /></Guard>
<Guard><Pareto /></Guard>
<Guard><Faster /></Guard>
<Guard><Cheaper /></Guard>
<Guard><OpenSource /></Guard>
<Guard><Compatibility samples={samples} /></Guard>
<Guard><Platform /></Guard>
<Guard>
<YStack id="plans" data-hz-view="plans" width="100%" maxW={1240} mx="auto" px="var(--page-gutter)" py={56} gap="$6" $md={{ py: 80 }}>
<Plans heading="h2" label="Hanzo.Pricing" title={phrase('Pricing')} ask={phrase('Try Hanzo')} footer={false} />
<Unsure />
</YStack>
</Guard>
<Guard><Trusted /></Guard>
</Suspense>
</main>
<SiteFooter />
</Box>
)
}