mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
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.
232 lines
11 KiB
TypeScript
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;
|