Files

176 lines
7.0 KiB
TypeScript

'use client'
import React from "react";
import { motion } from "@/components/motion";
import { Activity, Users, Zap, Cpu, Server, Network } from "lucide-react";
import { View, YStack, XStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Line } from '@/components/type'
import { Lede, Title } from '@hanzo/ui/marketing'
/**
* The tile each claim sits in.
*
* A COLUMN, not a block: gui's `Text` is `display: inline`, so the heading and
* the paragraph under it would share a line unless each is a flex item.
*/
const CARD = {
p: 24,
rounded: 'var(--radius-xl)',
borderWidth: 1,
borderColor: 'var(--border)',
background: 'linear-gradient(to bottom right, color-mix(in oklab, var(--pure-white) 20%, transparent), transparent)',
} as const
/** The strip of three facts under them. */
const FACT = {
p: 16,
rounded: 'var(--radius-lg)',
borderWidth: 1,
borderColor: 'var(--neutral-800)',
background: 'color-mix(in oklab, var(--pure-black) 40%, transparent)',
} as const
/** A meter: the track, and the fill whose width is the only thing that varies. */
function Meter({ fill }: { fill: `${number}%` }) {
return (
<View width="100%" height={8} rounded="var(--radius-full)" overflow="hidden" bg="var(--neutral-800)">
<View width={fill} height="100%" rounded="var(--radius-full)" bg="var(--primary)" />
</View>
)
}
const Scaling = () => {
return (
<YStack
render="section"
py={80}
px={16}
$sm={{ px: 24 }}
$lg={{ px: 32 }}
background="linear-gradient(to bottom, var(--background), color-mix(in oklab, var(--pure-white) 10%, transparent))"
>
<YStack width="100%" maxW={1280} mx="auto">
<View
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5 }} />}
display="block"
mb={64}
$platform-web={{ textAlign: 'center' }}
>
<Title quiet mb={16}>
What happens as it grows
</Title>
<Lede maxW={768} mx="auto" mt="$3.5">
The parts that scale, and the parts that are simply not in the way
</Lede>
</View>
<Grid columns={{ min: 300, max: 3 }} gap={32}>
<YStack
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.1 }} />}
{...CARD}
>
<View mb={16}>
<Activity width={32} height={32} color="var(--foreground)" />
</View>
<Line render="h3" size="xl" weight="600" marginBottom={8}>A change costs its audience</Line>
<Line render="p" tone="soft">
A record is written once and handed to the subscribers already
attached to it. The work is the number of people watching, not the
size of the collection they are watching.
</Line>
<YStack mt={24} pt={24} borderTopWidth={1} borderColor="var(--neutral-800)">
<XStack items="center" justify="space-between" mb={8}>
<Line size="sm" tone="muted">Cost of a write</Line>
<Line weight="500">Per subscriber</Line>
</XStack>
<Meter fill="100%" />
</YStack>
</YStack>
<YStack
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.2 }} />}
{...CARD}
>
<View mb={16}>
<Users width={32} height={32} color="var(--foreground)" />
</View>
<Line render="h3" size="xl" weight="600" marginBottom={8}>A stream is a response</Line>
<Line render="p" tone="soft">
Not a process, not a socket server beside the API — an HTTP
response left open. They are cheap to hold, and one that goes away
releases what it held without anything needing to be told.
</Line>
<YStack mt={24} pt={24} borderTopWidth={1} borderColor="var(--neutral-800)">
<XStack items="center" justify="space-between" mb={8}>
<Line size="sm" tone="muted">Per subscriber</Line>
<Line weight="500">One open response</Line>
</XStack>
<Meter fill="95%" />
</YStack>
</YStack>
<YStack
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.3 }} />}
{...CARD}
>
<View mb={16}>
<Network width={32} height={32} color="var(--foreground)" />
</View>
<Line render="h3" size="xl" weight="600" marginBottom={8}>Isolated by tenant</Line>
<Line render="p" tone="soft">
Every org's subscribers sit on that org's own broker, over that
org's own data. Adding a tenant adds a broker rather than widening
a shared one, so nobody's growth is anybody else's problem.
</Line>
<YStack mt={24} pt={24} borderTopWidth={1} borderColor="var(--neutral-800)">
<XStack items="center" justify="space-between" mb={8}>
<Line size="sm" tone="muted">Blast radius</Line>
<Line weight="500">One tenant</Line>
</XStack>
<Meter fill="90%" />
</YStack>
</YStack>
</Grid>
<View
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.4 }} />}
display="block"
mt={64}
>
<Grid columns={{ min: 220, max: 3 }} gap={24}>
<XStack items="center" gap={16} {...FACT}>
<Zap width={32} height={32} color="var(--foreground)" />
<YStack>
<Line render="h4" weight="500">No polling</Line>
<Line render="p" size="sm" tone="muted">The client asks once, not every second</Line>
</YStack>
</XStack>
<XStack items="center" gap={16} {...FACT}>
<Cpu width={32} height={32} color="var(--foreground)" />
<YStack>
<Line render="h4" weight="500">Reconnects itself</Line>
<Line render="p" size="sm" tone="muted">A dropped stream reopens with a fresh grant</Line>
</YStack>
</XStack>
<XStack items="center" gap={16} {...FACT}>
<Server width={32} height={32} color="var(--foreground)" />
<YStack>
<Line render="h4" weight="500">Nothing extra to run</Line>
<Line render="p" size="sm" tone="muted">The same binary that serves the API serves the stream</Line>
</YStack>
</XStack>
</Grid>
</View>
</YStack>
</YStack>
);
};
export default Scaling;