Files

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. */