mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
The bar stood in the page's flow, clear at rest, so a first screen whose ground is a picture or a light started below it, and the bar read as a black band over the html's #000 ground: above the creative models' paintings, the kit pages' glow and the lit product heroes. It is glass now at rest and scrolled ($background/72 under the chrome blur; the scroll listener is gone), and such a first screen spreads `under(pt)` from components/shell/dock.ts: up by the bar's height, the same back on its top padding, so its ground starts at the top of the window behind the glass and no word moves. On Splash (the six creative models and Zen), PageHero, /edge, /identity, /functions, /playground, /operative, /realtime and /enso. On /beta the site's bar at rest keeps the glass and paints the focused pages' fixed glow on it (components/shell/focus.tsx), so the light runs on unbroken. 72% is kept: over Zen's painting the nav's ink holds 5.1:1 at .72 and falls to 3.7:1 at .6. The beta strip is polished curved steel in CSS gradients: a faint top line, a ridge 5px down, the flat dark under the words, a temper line and the bright 1px edge at the bottom; one `/` light at 70 degrees, resting mid-strip, moved by transform, brighter where it crosses the ridge and the edge than under the words. The 1px/3px grain that read as stripes is gone, and so is the root script that set --beta-tilt on <html> every pointer frame on every page, restyling the whole document and ignoring reduced motion. White words hold 5.4:1 against the brightest steel under them, where the live strip measured 4.35:1 at 1440 and 3.45:1 on a phone. Gates: e2e/gates/glass.spec.ts and e2e/gates/steel.spec.ts. The composer gate reads its rest once the theme's colours have eased in: read straight after the input appeared, the pill's border was a value on its way to its token (rgba(137…), rgba(175…) on two CI runs) and failed the check that the border keeps one value through focus. Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
263 lines
11 KiB
TypeScript
263 lines
11 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* /functions — serverless handlers on a cluster you already run.
|
|
*
|
|
* NO TAILWIND. The four section frames are `components/marketing/band`'s `Band`
|
|
* and `Head`; @hanzo/ui's marketing components carry the type and the controls
|
|
* (`Display`, `Lede`, `Title`, `Chip`, `Action`, `Line`); everything left is
|
|
* gui props over @hanzo/design's tokens. The six capability cards and four use
|
|
* cases are DATA — they were the same markup written ten 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 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.
|
|
* - `Action` is the one button shape at its two weights.
|
|
*
|
|
* The sample keeps ONE type rung. It was `text-sm sm:text-base`, and a code
|
|
* sample's size is not a role this site states at a breakpoint — the reference
|
|
* conversion (components/blockchain/BlockchainProductLayout) sets a single
|
|
* `--text-sm` on its source frames and this matches it. `Lattice` is
|
|
* @hanzo/ui's ruled backdrop, placed by its own props.
|
|
*/
|
|
|
|
import React, { type ReactNode } from 'react';
|
|
import { motion } from "@/components/motion";
|
|
import { Code, Terminal, Database, Server, Network, 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, Chip, Display, Lattice, Lede, Line } from '@hanzo/ui/marketing'
|
|
import { under } from '@/components/shell/dock'
|
|
|
|
/** 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. One table answers
|
|
* for every product page, so this one cannot say something its neighbours do
|
|
* not. */
|
|
const PRODUCT = product('functions')
|
|
|
|
/** 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 inks this page draws body copy and marks in. */
|
|
const SOFT = 'color-mix(in oklab, var(--foreground) 80%, transparent)'
|
|
const WASH = 'color-mix(in oklab, var(--primary) 10%, transparent)'
|
|
const EDGE = 'color-mix(in oklab, var(--pure-white) 30%, transparent)'
|
|
|
|
/** A `color-mix()` ink is not a value gui's colour props type, so a frame's
|
|
* ground and edge ride `$platform-web`, which gui still writes as a class. */
|
|
const CARD = { p: 24, rounded: 'var(--radius-xl)', borderWidth: 1, '$platform-web': { borderColor: EDGE, 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
|
|
|
|
/** The pair of controls in the hero, filled and outlined. */
|
|
const CENTER = { justify: "center" } as const
|
|
const OUTLINE = { justify: "center", borderColor: EDGE } as const
|
|
|
|
const FEATURES = [
|
|
{ icon: Code, title: 'Python, Go, Node.js, Rust', body: 'Name a runtime and point at a handler file. When the language you want is not one of them, bring a container you built yourself and deploy that instead.' },
|
|
{ icon: Terminal, title: 'Warm before the request', body: 'Pools are prewarmed, so a function is not booting on the request that needed it. Scale to zero is still the default when nothing is calling.' },
|
|
{ icon: Database, title: 'On your own cluster', body: 'Install it with Helm into Kubernetes you already run. Functions are ordinary workloads there, so quotas, node pools and network policy keep meaning what they meant.' },
|
|
{ icon: Server, title: 'A GPU is a normal ask', body: 'A function can request an NVIDIA GPU and be scheduled onto a node that has one. That is what lets model work be a function instead of a service somebody has to keep alive.' },
|
|
{ icon: Network, title: 'Triggers, not just HTTP', body: 'An HTTP route, a cron schedule, a Kafka topic, a RabbitMQ queue. The trigger is its own object, so the same handler can answer to several without knowing about any of them.' },
|
|
{ icon: Shield, title: 'Versions you can move between', body: 'Blue/green and canary between versions of a function, so backing out a bad deploy is a traffic decision rather than another build.' },
|
|
] as const
|
|
|
|
const USE_CASES = [
|
|
{
|
|
title: 'API Backends',
|
|
body: 'Build complete REST or GraphQL APIs with automatic scaling and no server management.',
|
|
points: [
|
|
'Custom middleware for authentication and validation',
|
|
'API versioning and documentation generation',
|
|
'Automatic OpenAPI schema generation',
|
|
],
|
|
},
|
|
{
|
|
title: 'Real-time Processing',
|
|
body: 'Process events in real-time with automatic scaling for traffic spikes.',
|
|
points: [
|
|
'Webhook handlers for third-party services',
|
|
'Event-driven data transformation and enrichment',
|
|
'Real-time analytics processing',
|
|
],
|
|
},
|
|
{
|
|
title: 'AI/ML Inference',
|
|
body: 'Deploy machine learning models for inference with built-in GPU acceleration.',
|
|
points: [
|
|
'Image and text classification',
|
|
'AI chatbots and virtual assistants',
|
|
'Real-time recommendations',
|
|
],
|
|
},
|
|
{
|
|
title: 'Scheduled Tasks',
|
|
body: 'Run tasks on a schedule without maintaining a dedicated server.',
|
|
points: [
|
|
'Periodic data cleanup and maintenance',
|
|
'Scheduled reports and notifications',
|
|
'Recurring data integration and ETL processes',
|
|
],
|
|
},
|
|
] as const
|
|
|
|
const Functions = ({ handler }: { handler: ReactNode }) => {
|
|
return (
|
|
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)' }}>
|
|
|
|
{/* Hero Section */}
|
|
<Box render="section" position="relative" {...under(80)} pb={80} $lg={{ pt: under(128).pt, pb: 128 }}>
|
|
<Box
|
|
position="absolute"
|
|
t={0}
|
|
r={0}
|
|
b={0}
|
|
l={0}
|
|
backgroundImage="linear-gradient(to bottom in oklab, color-mix(in oklab, var(--pure-white) 10%, transparent), transparent)"
|
|
/>
|
|
<Box
|
|
position="relative"
|
|
z="var(--z-raised)"
|
|
width="100%"
|
|
maxW={1280}
|
|
mx="auto"
|
|
px={16}
|
|
$sm={{ px: 24 }}
|
|
$lg={{ px: 32 }}
|
|
>
|
|
<Box width="100%" maxW={768} mx="auto" mb={64} $platform-web={{ textAlign: 'center' }}>
|
|
<Chip
|
|
mb={16}
|
|
px={16}
|
|
py={4}
|
|
borderColor="var(--border)"
|
|
bg={WASH}
|
|
>
|
|
<Line fontWeight="500" color="var(--foreground)">Serverless Computing</Line>
|
|
</Chip>
|
|
<Display
|
|
mb={24}
|
|
>
|
|
Hanzo Functions
|
|
</Display>
|
|
<Lede mb={32}>
|
|
Functions that run on Kubernetes. Hand it a handler and a runtime; it builds the image, deploys it, routes to it, and keeps as many copies alive as the traffic needs — down to none.
|
|
</Lede>
|
|
<YStack gap={16} justify="center" $sm={{ flexDirection: 'row' }}>
|
|
<Action href="/login" fill {...CENTER}>
|
|
Try Hanzo
|
|
</Action>
|
|
<Action href={`${DOCS}/docs/cli/function`} {...OUTLINE}>
|
|
View Documentation
|
|
</Action>
|
|
</YStack>
|
|
</Box>
|
|
|
|
{/* Hero Visual */}
|
|
<Box position="relative" overflow="hidden" p={32} rounded="var(--radius-xl)" borderWidth={1} borderColor="var(--border)" $platform-web={{ backgroundColor: WASH }}>
|
|
<Lattice
|
|
position="absolute"
|
|
inset={0}
|
|
maskImage="linear-gradient(to bottom, white, transparent)"
|
|
/>
|
|
{handler}
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
|
|
{PRODUCT && <AgentPrompt product={PRODUCT} />}
|
|
|
|
{/* Features */}
|
|
<Band rule={false} pad="$11" measure={1280}>
|
|
<Head
|
|
title="What you get"
|
|
lede="A handler file, a trigger, and the cluster you already run"
|
|
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="var(--foreground)" />
|
|
</Box>
|
|
<Line render="h3" display="block" mb={8} fontSize="$6" lineHeight="1.4" fontWeight="700" color="var(--text-primary)">
|
|
{title}
|
|
</Line>
|
|
<Line render="p" display="block" color={SOFT}>
|
|
{body}
|
|
</Line>
|
|
</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="Common Use Cases"
|
|
lede="Hanzo Functions powers a wide range of serverless applications"
|
|
measure={768}
|
|
/>
|
|
|
|
<Grid columns={DUO} gap={32}>
|
|
{USE_CASES.map(({ title, body, points }) => (
|
|
<Box key={title} {...PANEL}>
|
|
<Line render="h3" display="block" mb={16} fontSize="$7" lineHeight="1.3333" fontWeight="700" color="var(--text-primary)">
|
|
{title}
|
|
</Line>
|
|
<Line render="p" display="block" mb={16} color={SOFT}>
|
|
{body}
|
|
</Line>
|
|
<YStack render="ul" gap={8}>
|
|
{points.map((point) => (
|
|
<XStack key={point} render="li" items="flex-start">
|
|
<Line mr={8} color="var(--foreground)">•</Line>
|
|
<Line color={SOFT}>{point}</Line>
|
|
</XStack>
|
|
))}
|
|
</YStack>
|
|
</Box>
|
|
))}
|
|
</Grid>
|
|
</Band>
|
|
|
|
{PRODUCT && <Related product={PRODUCT} />}
|
|
|
|
<ProductFooter slug="functions" name="Functions" />
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default Functions;
|