mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
211 lines
8.5 KiB
TypeScript
211 lines
8.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Box as BoxIcon, Monitor, SquareTerminal } from 'lucide-react'
|
|
import { Surface } from '@/components/product/Surface'
|
|
import { Box, Text, View, YStack } from '@hanzo/ui'
|
|
import { tracks } from '@hanzo/ui/grid'
|
|
import { Lede, Title } from '@hanzo/ui/marketing'
|
|
|
|
/**
|
|
* Compute — one runtime, and the kinds of work it takes.
|
|
*
|
|
* TWO DEPTHS, on purpose. The SPECTRUM names all six kinds and orders them by
|
|
* how much of a machine each one holds: a function borrows one for the length
|
|
* of a call, Kubernetes IS the machine. That ordering is the argument — they
|
|
* are not six products to choose between, they are one runtime addressed six
|
|
* ways, so moving along it is a deploy target rather than a migration.
|
|
*
|
|
* Under it, the three ISOLATIONS a reader can actually inspect. A container, a
|
|
* whole machine and a leased shell each have their own API surface, and picking
|
|
* one draws the console that answers for it.
|
|
*
|
|
* EACH ISOLATION IS DRAWN, not filmed: the console showing that kind of work,
|
|
* with the route it answers on in the footer and the compute rail beside it.
|
|
* The route under each one is the same one a reader can curl.
|
|
*
|
|
* The copy here says what the ISOLATION is for. It states no latency, no boot
|
|
* time and no capacity, because this repo has no measurement for any of them
|
|
* and a number without one is the thing every other claim on this site is
|
|
* checked against.
|
|
*/
|
|
|
|
/** The six, lightest hold on a machine first. */
|
|
const SPECTRUM: [string, string][] = [
|
|
['Functions', 'Scale to zero between calls.'],
|
|
['Containers', 'Your image, deployed and addressable.'],
|
|
['Sandboxes', 'Isolated shells for agents and untrusted code.'],
|
|
['Machines', 'A whole machine, by the second.'],
|
|
['GPUs', 'Accelerators attached to any of them.'],
|
|
['Kubernetes', 'The substrate all of it runs on.'],
|
|
]
|
|
const KINDS = [
|
|
{
|
|
id: 'containers',
|
|
title: 'Containers',
|
|
icon: BoxIcon,
|
|
blurb:
|
|
'Deploy an image and invoke it. Deployments, triggers, secrets, metrics and logs are all part of the same surface, so a function you ship is a function you can watch.',
|
|
route: '/v1/functions',
|
|
/** The catalog leaf the console rail marks while this kind is open. */
|
|
leaf: 'functions',
|
|
shape: 'list',
|
|
},
|
|
{
|
|
id: 'vms',
|
|
title: 'Machines',
|
|
icon: Monitor,
|
|
blurb:
|
|
'A whole machine when a container is the wrong shape — provisioned on our account or folded in from yours, each one carrying an agent you address directly.',
|
|
route: '/v1/machines',
|
|
leaf: 'machines',
|
|
shape: 'list',
|
|
},
|
|
{
|
|
id: 'sandboxes',
|
|
title: 'Sandboxes',
|
|
icon: SquareTerminal,
|
|
blurb:
|
|
'An isolated filesystem and shell, leased for as long as the work runs and reaped when it stops. Read and write files, exec into it, end it — the isolation an agent runs inside.',
|
|
route: '/v1/sandboxes',
|
|
// A shell is what the product IS, so the console draws one.
|
|
leaf: undefined,
|
|
shape: 'shell',
|
|
},
|
|
] as const
|
|
|
|
/** The spectrum's track list: as many as fit at 170px, six at most. */
|
|
const SPECTRUM_TRACKS = tracks({ min: 170, max: 6 }, '24px')
|
|
|
|
export default function Workloads() {
|
|
const [at, setAt] = useState(0)
|
|
|
|
return (
|
|
<Box
|
|
render="section"
|
|
borderTopWidth={1}
|
|
borderColor="var(--neutral-900)"
|
|
px="$4"
|
|
py="$12"
|
|
$sm={{ px: '$5', py: '$14' }}
|
|
$lg={{ px: '$6' }}
|
|
>
|
|
<Box width="100%" maxW={1152} mx="auto">
|
|
{/* `<h2>`, because the fold carries the page's one `<h1>`. Left-aligned
|
|
and full measure, like every other band on this page — a centred
|
|
heading in a column of left-aligned ones reads as a different page. */}
|
|
<Title maxW={1024}>
|
|
One runtime for every kind of work.
|
|
</Title>
|
|
<Lede mt={24} maxW={672}>
|
|
The same plane, held for a millisecond or held for a month. What
|
|
changes is how much of a machine the work needs, not what you have to
|
|
learn.
|
|
</Lede>
|
|
|
|
{/* The spectrum. The rule beneath it brightens left to right, so the
|
|
ordering reads as one continuum before a single label is read. */}
|
|
<Box mt="$9">
|
|
<View aria-hidden height={1} width="100%" backgroundImage="linear-gradient(to right, var(--white-10), var(--white-55))" />
|
|
{/* An ordered list on gui's grid props, its tracks from
|
|
`@hanzo/ui/grid`'s own `tracks()` rather than a second copy of
|
|
the arithmetic. */}
|
|
<View render="ol" display="grid" gridTemplateColumns={SPECTRUM_TRACKS} columnGap={24} rowGap={28} m={0} p={0} pt={24}>
|
|
{SPECTRUM.map(([name, line]) => (
|
|
<YStack key={name} render="li">
|
|
<Text render="p" fontSize="$3" lineHeight="1.3125rem" fontWeight="500" color="$ink">
|
|
{name}
|
|
</Text>
|
|
<Text render="p" mt={6} fontSize="$2" lineHeight="1.3203rem" color="var(--muted-foreground)">
|
|
{line}
|
|
</Text>
|
|
</YStack>
|
|
))}
|
|
</View>
|
|
</Box>
|
|
|
|
{/* One column until `$lg`, where the control takes a fixed 22rem rail
|
|
beside the surface. Stating both tracks at every width gives a phone
|
|
a 352px rail inside a 358px measure and pushes the window off the
|
|
side. */}
|
|
<Box
|
|
display="grid"
|
|
gridTemplateColumns="minmax(0,1fr)"
|
|
gap="$6"
|
|
mt="$11"
|
|
$lg={{ gridTemplateColumns: 'minmax(0,22rem) minmax(0,1fr)', gap: '$8' }}
|
|
>
|
|
{/* The three, as one control. Buttons, not links: this swaps a picture
|
|
on the page rather than going anywhere. */}
|
|
<Box>
|
|
{KINDS.map((k, i) => {
|
|
const on = i === at
|
|
const Icon = k.icon
|
|
return (
|
|
<Box
|
|
key={k.id}
|
|
render="button"
|
|
onClick={() => setAt(i)}
|
|
aria-current={on}
|
|
width="100%"
|
|
borderTopWidth={1}
|
|
borderTopColor="var(--neutral-900)"
|
|
borderLeftWidth={on ? 2 : 0}
|
|
borderLeftColor="var(--pure-white)"
|
|
py="$5"
|
|
pl={20}
|
|
borderBottomWidth={i === KINDS.length - 1 ? 1 : 0}
|
|
borderBottomColor="var(--neutral-900)"
|
|
hoverStyle={on ? undefined : { bg: 'var(--glass)' }}
|
|
$platform-web={{ textAlign: 'left' }}
|
|
>
|
|
{/* SPANS, not a Stack. A `button`'s content model is phrasing
|
|
content, so a Stack's div inside one is invalid markup —
|
|
and the text extractor reads it as a block, which is how a
|
|
title and its blurb came to sit on two lines where the page
|
|
draws one. */}
|
|
<Text display="flex" items="center" gap={10}>
|
|
<Text display="inline-flex" color={on ? '$ink' : 'var(--neutral-600)'}>
|
|
<Icon size={16} />
|
|
</Text>
|
|
<Text fontSize="$4" lineHeight="1.4rem" fontWeight="500" color={on ? '$ink' : 'var(--neutral-500)'}>
|
|
{k.title}
|
|
</Text>
|
|
</Text>
|
|
{/* Only the open one explains itself — three paragraphs at
|
|
once is a wall, and the point of the control is that you
|
|
are looking at one thing. */}
|
|
{on && (
|
|
<>
|
|
<Text display="block" mt={12} fontSize="$2" lineHeight="1.3203rem" color="var(--muted-foreground)">
|
|
{k.blurb}
|
|
</Text>
|
|
<Text display="block" mt={12} fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--text-tertiary)">
|
|
api.hanzo.ai{k.route}
|
|
</Text>
|
|
</>
|
|
)}
|
|
</Box>
|
|
)
|
|
})}
|
|
</Box>
|
|
|
|
{/* One surface, drawn for whichever kind is open. It is DOM, so the
|
|
swap costs no fetch and there is nothing to preload. */}
|
|
<Box position="relative" minW={0}>
|
|
<Surface
|
|
name={KINDS[at].title}
|
|
api={KINDS[at].route}
|
|
rail="compute"
|
|
here={KINDS[at].leaf}
|
|
shape={KINDS[at].shape}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
/** `last:border-b` — the run closes itself. */
|