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

232 lines
11 KiB
TypeScript

'use client'
/**
* /edge — the on-device runtime, and when to reach for it.
*
* NO TAILWIND. The four section frames are `components/marketing/band`'s `Band`
* and `Head` — the rule, the rhythm, the gutter, the measure and the centred
* head this page used to write out by hand at each of them — the roles carry
* the type and the controls (`Display`, `Lede`, `Chip`, `Action`, `Line`), and
* everything left is gui props over @hanzo/design's tokens. The three hero
* tiles, the six capability cards and the four use cases
* are DATA; they were the same markup written thirteen times.
*
* Three things the swap moves, each deliberate, each the shared shape winning
* over this page's own copy of it:
*
* - the section headings take `Title quiet` and stop stating
* `text-3xl font-bold`, so they step 3xl -> 4xl at 640 and are CENTRED. They
* were `ChromeText` inside a `text-center` block, and ChromeText's own
* `flex flex-col items-start` wrapper shrink-wrapped each one to the LEFT
* while the paragraph under it centred — the page has been rendering a left
* heading over a centred lede.
* - the closing rule takes `Band`'s `--neutral-900` hairline rather than this
* page's `--neutral-800`, and its gutter steps at 640/1024 like every other
* band on the site rather than staying at 16 everywhere.
* - `Action` is the one button shape at its two weights, so `px-8 py-6
* text-lg` and `px-6 py-3 rounded-md` become the same control.
*
* `Lattice` is @hanzo/ui's ruled backdrop, placed by its own props.
*/
import React from 'react';
import { motion } from "@/components/motion";
import { Zap, Globe, Network, Server, Cloud, Code, Shield } from "lucide-react";
import { ProductFooter } from "@/components/products/ProductFooter"
import { AgentPrompt } from '@/components/product/AgentPrompt'
import { Related } from '@/components/product/Related'
import { Box, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Band, Head } from '@/components/marketing/band'
import { product } from '@/lib/product'
import { DOCS } from '@/components/home/nav-data'
import { Action, Line, type LineProps } from '@hanzo/ui/marketing'
import { Fold } from '@/components/marketing/fold'
import { Steps } from '@/components/preview/steps'
import { phrase } from '@hanzo/i18n'
import { useI18n } from '@hanzo/i18n/react'
/** What the two shared blocks say about this product: the audited command, the
* route the catalog proves it by, and the docs page or null. The table has no
* edge command and docs publishes no page for it, so each block is silent on
* both rather than guessing. */
const PRODUCT = product('edge')
/** How narrow a column may get before a grid drops one, per shape. */
const DECK = { min: 304, max: 3 } as const
const DUO = { min: 320, max: 2 } as const
/** The three inks this page draws body copy and marks in. */
const SOFT = 'color-mix(in oklab, var(--foreground) 80%, transparent)'
const DIM = 'color-mix(in oklab, var(--foreground) 70%, transparent)'
const WASH = 'color-mix(in oklab, var(--primary) 10%, transparent)'
/** A `color-mix()` ink is not a value gui's colour props type, so a frame's
* wash rides `$platform-web`, which gui still writes as a class. */
const CARD = { p: 24, rounded: 'var(--radius-xl)', borderWidth: 1, borderColor: 'var(--border)', '$platform-web': { backgroundColor: WASH } } as const
const PANEL = {
p: 32,
rounded: 'var(--radius-xl)',
borderWidth: 1,
borderColor: 'var(--border)',
'$platform-web': { backgroundColor: 'color-mix(in oklab, var(--primary) 5%, transparent)' },
} as const
/** A card's heading, at the ink a heading takes from the base sheet. */
const Heading = (p: LineProps) => <Line render="h3" display="block" fontWeight="700" color="var(--text-primary)" {...p} />
/** A paragraph in the page's soft ink. */
const Body = (p: LineProps) => <Line render="p" display="block" color={SOFT} {...p} />
/** The pair of controls in the hero, filled and outlined. */
const CENTER = { justify: "center" } as const
const OUTLINE = { justify: "center", borderColor: "var(--border)" } as const
/** The three tiles under the hero — where the runtime runs, and on what terms. */
const PLACES = [
{ icon: Globe, title: phrase('Where it runs'), body: phrase('macOS on Apple Silicon and Intel, Linux on x86 and ARM, and modern browsers through WebAssembly') },
{ icon: Zap, title: phrase('No network at all'), body: phrase('Weights on disk, tokens out of local memory. There is no request, so there is nothing to be slow') },
{ icon: Network, title: phrase('Fetched once'), body: phrase('Name a Hugging Face repo and it downloads and caches the weights. After that, offline') },
] as const
const FEATURES = [
{ icon: Code, title: phrase('A server on localhost'), body: phrase('A local inference endpoint on a port you pick. Point a client at it and the rest of your code does not change.') },
{ icon: Cloud, title: phrase('Quantized as a first language'), body: phrase('GGUF is the native format — Q4_K, Q5_K, Q8_0. A 4B model at Q4_K_M is about 2.5GB on disk, which is what makes a phone a plausible place to run one.') },
{ icon: Server, title: phrase('It uses what it finds'), body: phrase('Metal on Apple Silicon, CUDA where there is an NVIDIA card, AVX2 or AVX-512 on CPU. Detected at startup rather than configured by you.') },
{ icon: Globe, title: phrase('Zen models sized for devices'), body: phrase('zen3-nano at 600M for embedded work and zen-eco at 4B for phones and tablets, both published pre-quantized.') },
{ icon: Network, title: phrase('Token by token'), body: phrase('Streaming over server-sent events from the local server, or a callback from the Rust API, so a UI starts drawing before generation finishes.') },
{ icon: Shield, title: phrase('The prompt never leaves'), body: phrase('Nothing is uploaded and there is no key to leak. The prompt and the output stay in the process that made them, which is the whole reason to run a model here.') },
] as const
const USE_CASES = [
{
title: phrase('Apps that work with no signal'),
body: phrase('The model is a file on disk, so the feature does not stop existing when the connection does.'),
points: [
phrase('A desktop app that drafts and summarizes without a round trip'),
phrase('Field tools on a laptop, on a site with no coverage'),
phrase('Anything that has to keep working on a plane'),
],
},
{
title: phrase('Text that cannot leave the building'),
body: phrase('Some prompts are a legal problem the moment they cross a network boundary. This is the answer to those.'),
points: [
phrase('Clinical and legal text processed on the workstation that holds it'),
phrase('On-premise deployments with no egress at all'),
phrase('Personal data handled on the device it belongs to'),
],
},
{
title: phrase('Inside the browser tab'),
body: phrase('The WebAssembly build runs the model in the page, with nothing behind it — so a visitor costs you bandwidth once and compute never.'),
points: [
phrase('A demo that does not bill per visitor'),
phrase('An assistant inside a web app you already ship'),
phrase('Classification done client-side, before anything is uploaded'),
],
},
{
title: phrase('Small hardware'),
body: phrase('ARM64 Linux is a production target. Cortex-A class boards are experimental, and the 600M model is the one that fits them.'),
points: [
phrase('A 600M model on a single-board computer'),
phrase('An ARM server doing batch work on its own'),
phrase('Kiosks and appliances with no account to sign in to'),
],
},
] as const
const Edge = () => {
const { t } = useI18n()
return (
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)' }}>
<Fold
label="On-device inference"
title="Hanzo Edge"
lede="Run a model on the machine in front of you."
actions={
<>
<Action href="https://github.com/hanzoai/edge" fill {...CENTER}>
{t('View on GitHub')}
</Action>
<Action href={DOCS} {...OUTLINE}>
{t('View Documentation')}
</Action>
</>
}
preview={<Steps title="Where it runs" steps={PLACES.map((x) => ({ title: x.title, body: x.body }))} />}
/>
{PRODUCT && <AgentPrompt product={PRODUCT} />}
{/* Features */}
<Band rule={false} pad="$11" measure={1280}>
<Head
title={t('What the runtime does')}
lede={t('One Rust binary, a core crate to embed, a WASM build for the browser, and a local server for everything else')}
measure={768}
/>
<Grid columns={DECK} gap={32}>
{FEATURES.map(({ icon: Icon, title, body }, index) => (
<Box
key={title}
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: index * 0.1 }} viewport={{ once: true }} />}
{...CARD}
>
<Box mb={16}>
<Icon width={40} height={40} color={DIM} />
</Box>
<Heading mb={8} fontSize="$6" lineHeight="1.4">
{t(title)}
</Heading>
<Body>{t(body)}</Body>
</Box>
))}
</Grid>
</Band>
{/* Use Cases */}
<Band
rule={false}
pad="$11"
measure={1280}
ground="linear-gradient(to bottom in oklab, var(--background), color-mix(in oklab, var(--pure-white) 10%, transparent))"
>
<Head
title={t('When to reach for it')}
lede={t('Use Edge when the data cannot leave, the network cannot be relied on, or a per-call bill is the wrong shape. Use Hanzo Engine when you need full precision or many users at once.')}
measure={768}
/>
<Grid columns={DUO} gap={32}>
{USE_CASES.map(({ title, body, points }) => (
<Box key={title} {...PANEL}>
<Heading mb={16} fontSize="$7" lineHeight="1.3333">
{t(title)}
</Heading>
<Body mb={16}>{t(body)}</Body>
<YStack render="ul" gap={8}>
{points.map((point) => (
<XStack key={point} render="li" items="flex-start">
<Line marginInlineEnd={8} color={DIM}>•</Line>
<Line color={SOFT}>{t(point)}</Line>
</XStack>
))}
</YStack>
</Box>
))}
</Grid>
</Band>
{PRODUCT && <Related product={PRODUCT} />}
<ProductFooter slug="edge" name="Edge" />
</Box>
);
};
export default Edge;