mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Every import, the catalog pin and the docs name @hanzo/app and github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and components/rooms.tsx is components/app.tsx.
955 lines
33 KiB
TypeScript
955 lines
33 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* /startups — the credits programme, and the form that applies to it.
|
|
*
|
|
* NO TAILWIND AND NO STYLE OBJECTS. @hanzo/ui's marketing components carry the
|
|
* type and the controls (`Display`, `Lede`, `Title`, `Chip`, `Action`), `Grid`
|
|
* wraps on the width a column actually has, and everything left is gui props
|
|
* over @hanzo/design's tokens: `Label` is the line over a field, `Card` a
|
|
* pillar, `Check` the box a choice ticks.
|
|
*
|
|
* NOT `Band`. This page's sections carry no rule, its gutter steps 16/32/48
|
|
* where every band on the site steps 16/24/32, and its rhythm (112/40, 32,
|
|
* 56, 96) is not on the band's ladder. A form page is not a product page, and
|
|
* borrowing the product band would restate all three.
|
|
*
|
|
* - `Field`/`Note` are gui's `Input`, because a placeholder's colour lives on
|
|
* `::placeholder` and `placeholderTextColor` is the prop that reaches it;
|
|
* without it the ink falls to @hanzo/design's `--text-disabled` (white at
|
|
* 30%) instead of gui's `$placeholderColor`, design's `--text-tertiary`.
|
|
* The two selects are @hanzo/ui's `Picker` in the same box.
|
|
* - `Cell` on `Toggle` — the site's surface whose ground answers a pointer.
|
|
* It adds no geometry, so an option in a multi-select wears it exactly as a
|
|
* grid tile does, and both grounds are its properties.
|
|
* - the privacy link says NOTHING. @hanzo/design's base already underlines a
|
|
* link inside a paragraph on approach, which is what every prose link on
|
|
* this site does; `More` is the wrong shape for one inside a sentence, since
|
|
* it is `inline-flex` at `text-sm` and stretching a link inside a line tears
|
|
* it.
|
|
*/
|
|
|
|
import React, { useState, type ReactNode } from 'react'
|
|
import { motion } from '@/components/motion'
|
|
import Link from 'next/link'
|
|
import {
|
|
Rocket,
|
|
ArrowRight,
|
|
CheckCircle2,
|
|
Coins,
|
|
Cpu,
|
|
Gift,
|
|
Building2,
|
|
Globe,
|
|
User,
|
|
Mail,
|
|
Briefcase,
|
|
Users,
|
|
Banknote,
|
|
Boxes,
|
|
Send,
|
|
type LucideIcon,
|
|
} from 'lucide-react'
|
|
import { Box, Picker, Text, View, XStack, YStack } from '@hanzo/ui'
|
|
import { Input, type GetProps } from '@hanzo/gui'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import { Action, Chip, Display, Lede, Title, Cell, Line } from '@hanzo/ui/marketing'
|
|
import { mix } from '@hanzo/app/lib/mix'
|
|
import { api } from '@hanzo/app/lib/api'
|
|
|
|
// Canonical cloud intake endpoint (public startup-program application), on the
|
|
// gateway of the host this page is open on.
|
|
const apply = () => `${api()}/v1/crm/applications`
|
|
|
|
/** How narrow a column may get before the grid drops one. */
|
|
const TRIO = { min: 280, max: 3 } as const
|
|
const DUO = { min: 260, max: 2 } as const
|
|
|
|
const MUTED = 'var(--muted-foreground)'
|
|
|
|
/** The two small rungs, each on the leading its utility paired it with: a ratio,
|
|
* so it follows the reader's type scale. `Line` takes it as written. */
|
|
const XS = { fontSize: '$1', lineHeight: '1.3333' } as const
|
|
const SM = { fontSize: '$2', lineHeight: '1.4286' } as const
|
|
|
|
/** The line over a field: its mark and its words, `htmlFor` where it names one control. */
|
|
const Label = ({ htmlFor, icon: Icon, children }: { htmlFor?: string; icon: LucideIcon; children: ReactNode }) => (
|
|
<Line
|
|
render="label"
|
|
htmlFor={htmlFor}
|
|
display="flex"
|
|
items="center"
|
|
gap={8}
|
|
mb={8}
|
|
{...SM}
|
|
fontWeight="500"
|
|
color="var(--foreground)"
|
|
>
|
|
<View render={<Icon width={14} height={14} color={MUTED} />} shrink={0} />
|
|
{children}
|
|
</Line>
|
|
)
|
|
|
|
/** The quiet half of a label — "(optional)", "(funds, angels)". */
|
|
const Aside = ({ children }: { children: ReactNode }) => (
|
|
<Text fontWeight="400" color={MUTED}>
|
|
{children}
|
|
</Text>
|
|
)
|
|
|
|
/** A pillar: its mark on a disc in the page's one accent at 10%, a title and a line. */
|
|
const Card = ({ icon: Icon, title, children }: { icon: LucideIcon; title: string; children: ReactNode }) => (
|
|
<Box minW={0} p={24} rounded="var(--radius-xl)" borderWidth={1} borderColor="var(--border)" bg={mix('var(--secondary)', 20)}>
|
|
<XStack items="center" justify="center" width={40} height={40} mb={16} rounded="var(--radius-lg)" bg={mix('var(--primary)', 10)}>
|
|
<Icon width={20} height={20} color={mix('var(--foreground)', 80)} />
|
|
</XStack>
|
|
<Line render="h3" display="block" mb={8} fontSize="$4" fontWeight="600" color="var(--text-primary)" lineHeight="1.5556">
|
|
{title}
|
|
</Line>
|
|
<Line render="p" display="block" fontSize="$2" color={MUTED} lineHeight="var(--leading-relaxed)">
|
|
{children}
|
|
</Line>
|
|
</Box>
|
|
)
|
|
|
|
/** The tick itself, drawn in the ground's ink. */
|
|
const Tick = ({ size }: { size: number }) => (
|
|
<svg width={size} height={size} fill="none" viewBox="0 0 24 24" stroke="var(--background)" strokeWidth={3}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
)
|
|
|
|
/** The box a choice ticks, at the two sizes this page ticks one: filled in the ink when on. */
|
|
const Check = ({ on, size, mt }: { on: boolean; size: 16 | 20; mt?: number }) => (
|
|
<View
|
|
render="span"
|
|
display="inline-flex"
|
|
width={size}
|
|
height={size}
|
|
shrink={0}
|
|
items="center"
|
|
justify="center"
|
|
mt={mt}
|
|
rounded="var(--radius)"
|
|
borderWidth={1}
|
|
borderStyle="solid"
|
|
transition="quickest"
|
|
bg={on ? 'var(--foreground)' : undefined}
|
|
borderColor={on ? 'var(--foreground)' : mix('var(--muted-foreground)', 40)}
|
|
>
|
|
{on && <Tick size={size === 16 ? 10 : 12} />}
|
|
</View>
|
|
)
|
|
|
|
/**
|
|
* One option in a multi-select, ticked or not.
|
|
*
|
|
* ONE ground on approach, for both the fund list and the use-case list — they
|
|
* were the same eleven utilities written twice, differing only in how far the
|
|
* tick sits from the top and how much room the row has.
|
|
*/
|
|
const Toggle = ({
|
|
on,
|
|
onClick,
|
|
align,
|
|
pad,
|
|
children,
|
|
}: {
|
|
on: boolean
|
|
onClick: () => void
|
|
align: 'center' | 'flex-start'
|
|
pad: number
|
|
children: React.ReactNode
|
|
}) => (
|
|
<Cell
|
|
render="button"
|
|
type="button"
|
|
onClick={onClick}
|
|
display="flex"
|
|
items={align}
|
|
gap={12}
|
|
p={pad}
|
|
rounded="var(--radius-lg)"
|
|
borderWidth={1}
|
|
borderStyle="solid"
|
|
text="left"
|
|
{...(on ? CHOSEN : CHOICE)}
|
|
>
|
|
<Check on={on} size={16} mt={align === 'flex-start' ? 2 : undefined} />
|
|
{children}
|
|
</Cell>
|
|
)
|
|
|
|
/** A choice in the form: its edge and its ground at rest and on approach. The
|
|
* chosen one holds its ground under the pointer. */
|
|
const CHOICE = {
|
|
borderColor: 'var(--border)',
|
|
bg: mix('var(--secondary)', 30),
|
|
hoverStyle: { bg: mix('var(--secondary)', 60) },
|
|
} as const
|
|
const CHOSEN = {
|
|
borderColor: mix('var(--pure-white)', 40),
|
|
bg: mix('var(--primary)', 10),
|
|
hoverStyle: { bg: mix('var(--primary)', 10) },
|
|
} as const
|
|
|
|
/** The box every field here is drawn in. `unstyled` zeroes the outline, so
|
|
* the focus ring @hanzo/design draws on every control is given its width back. */
|
|
const ENTRY = {
|
|
unstyled: true,
|
|
width: '100%',
|
|
px: 16,
|
|
py: 12,
|
|
rounded: 'var(--radius-lg)',
|
|
borderWidth: 1,
|
|
borderColor: 'var(--border)',
|
|
bg: 'var(--secondary)',
|
|
fontSize: '$2',
|
|
lineHeight: '$2',
|
|
color: 'var(--foreground)',
|
|
placeholderTextColor: '$placeholderColor',
|
|
focusVisibleStyle: { outlineWidth: 2 },
|
|
} as const
|
|
|
|
/** The two controls a form is made of: a line, and the same field `rows` tall. */
|
|
const Field = (props: GetProps<typeof Input>) => <Input {...ENTRY} {...props} />
|
|
|
|
const Note = (props: GetProps<typeof Input>) => <Input render="textarea" {...ENTRY} {...props} />
|
|
|
|
/** The platform's own select in the fields' box. */
|
|
const Choose = (props: React.ComponentProps<typeof Picker>) => (
|
|
<Picker
|
|
pl={16}
|
|
pr={16}
|
|
py={12}
|
|
minH={0}
|
|
rounded="var(--radius-lg)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg="var(--secondary)"
|
|
fontSize="$2"
|
|
$platform-web={{ lineHeight: '1.4286' }}
|
|
color="var(--foreground)"
|
|
{...props}
|
|
/>
|
|
)
|
|
|
|
const STAGES = [
|
|
{ id: 'idea', label: 'Idea' },
|
|
{ id: 'pre-seed', label: 'Pre-seed' },
|
|
{ id: 'seed', label: 'Seed' },
|
|
{ id: 'series-a', label: 'Series A' },
|
|
{ id: 'series-b-plus', label: 'Series B+' },
|
|
]
|
|
|
|
// Tier-1 funds recognized for the credits program. Free-text field captures any others.
|
|
const TIER1_FUNDS = [
|
|
'Andreessen Horowitz (a16z)',
|
|
'Sequoia',
|
|
'Benchmark',
|
|
'Greylock',
|
|
'Accel',
|
|
'Founders Fund',
|
|
'Lightspeed',
|
|
'Index Ventures',
|
|
'Bessemer',
|
|
'Khosla Ventures',
|
|
'GV (Google Ventures)',
|
|
'Techstars',
|
|
]
|
|
|
|
const USE_CASES = [
|
|
{ id: 'ai', label: 'AI & inference', description: 'Models, agents, RAG, one API for every model' },
|
|
{ id: 'deploy', label: 'Deploy', description: 'Apps, functions, edge, machines' },
|
|
{ id: 'data', label: 'Data', description: 'Vector, SQL, KV, object storage' },
|
|
{ id: 'compute', label: 'Compute', description: 'GPUs, training, batch jobs' },
|
|
]
|
|
|
|
const HEARD_OPTIONS = [
|
|
'Search',
|
|
'X / Twitter',
|
|
'LinkedIn',
|
|
'A friend or colleague',
|
|
'A Techstars connection',
|
|
'An investor',
|
|
'An event or conference',
|
|
'Hanzo docs or GitHub',
|
|
'Other',
|
|
]
|
|
|
|
const PILLARS = [
|
|
{
|
|
icon: Coins,
|
|
title: 'Start in minutes',
|
|
body: <>Creating an account is free. Add funds when you are ready and pay only for what you use — no subscription, no minimums.</>,
|
|
},
|
|
{
|
|
icon: Cpu,
|
|
title: 'Bring your own compute',
|
|
body: <>Connect your own GPUs and machines. Run cheap on hardware you already have — we meter the platform, not your iron.</>,
|
|
},
|
|
{
|
|
icon: Gift,
|
|
title: 'Up to $150,000 in credits',
|
|
body: <>For venture-backed startups. Application required, and tier-1 VC backing is required for the credits program.</>,
|
|
},
|
|
] as const
|
|
|
|
interface FormData {
|
|
company: string
|
|
website: string
|
|
contactName: string
|
|
email: string
|
|
role: string
|
|
stage: string
|
|
investors: string
|
|
tier1Investors: string[]
|
|
amountRaised: string
|
|
teamSize: string
|
|
building: string
|
|
useCases: string[]
|
|
infraSpend: string
|
|
byoCompute: boolean
|
|
byoHardware: string
|
|
techstarsBatch: string
|
|
heardVia: string
|
|
// Honeypot: must stay empty. Real users never see or fill this.
|
|
hp: string
|
|
}
|
|
|
|
const INITIAL_FORM: FormData = {
|
|
company: '',
|
|
website: '',
|
|
contactName: '',
|
|
email: '',
|
|
role: '',
|
|
stage: '',
|
|
investors: '',
|
|
tier1Investors: [],
|
|
amountRaised: '',
|
|
teamSize: '',
|
|
building: '',
|
|
useCases: [],
|
|
infraSpend: '',
|
|
byoCompute: false,
|
|
byoHardware: '',
|
|
techstarsBatch: '',
|
|
heardVia: '',
|
|
hp: '',
|
|
}
|
|
|
|
export default function StartupsPage() {
|
|
const [formData, setFormData] = useState<FormData>(INITIAL_FORM)
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [submitted, setSubmitted] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
|
) => {
|
|
setFormData({ ...formData, [e.target.name]: e.target.value })
|
|
}
|
|
|
|
const toggleInArray = (key: 'tier1Investors' | 'useCases', value: string) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[key]: prev[key].includes(value)
|
|
? prev[key].filter((v) => v !== value)
|
|
: [...prev[key], value],
|
|
}))
|
|
}
|
|
|
|
const validate = (): string | null => {
|
|
if (!formData.company.trim()) return 'Company name is required.'
|
|
if (!formData.contactName.trim()) return 'Your name is required.'
|
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) return 'A valid email is required.'
|
|
if (!formData.stage) return 'Please select your company stage.'
|
|
if (!formData.building.trim()) return 'Tell us what you are building.'
|
|
return null
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
const validationError = validate()
|
|
if (validationError) {
|
|
setError(validationError)
|
|
return
|
|
}
|
|
setIsSubmitting(true)
|
|
setError(null)
|
|
|
|
try {
|
|
const res = await fetch(apply(), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ ...formData, submittedAt: new Date().toISOString() }),
|
|
})
|
|
if (!res.ok) {
|
|
throw new Error(`Request failed (${res.status})`)
|
|
}
|
|
setSubmitted(true)
|
|
} catch (err) {
|
|
setError(
|
|
err instanceof Error ? err.message : 'Something went wrong. Please try again.'
|
|
)
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)' }}>
|
|
<Box render="main">
|
|
{/* Hero */}
|
|
<Box render="section" position="relative" pt={112} pb={40} px={16} $md={{ px: 32 }} $lg={{ px: 48 }}>
|
|
<Box width="100%" maxW={768} mx="auto" $platform-web={{ textAlign: 'center' }}>
|
|
<Box
|
|
render={<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} />}
|
|
mb={24}
|
|
>
|
|
<Chip px={12} py={4} fontSize="$1" lineHeight="1.3333" fontWeight="500" borderColor="var(--border)" bg={mix('var(--primary)', 10)} color="var(--foreground)">
|
|
<Rocket width={12} height={12} />
|
|
Techstars ’17 — built for founders
|
|
</Chip>
|
|
</Box>
|
|
|
|
<Display
|
|
render={<motion.h1 initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.05 }} />}
|
|
mb={20}
|
|
>
|
|
The Hanzo Startup Program
|
|
</Display>
|
|
|
|
<Lede
|
|
render={<motion.p initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.1 }} />}
|
|
width="100%"
|
|
maxW={672}
|
|
mx="auto"
|
|
>
|
|
Build frontier AI on the same infrastructure we run in production.
|
|
Everyone starts free. Venture-backed teams get up to $150,000 in
|
|
credits. Bring your own compute and we meter the platform, not your
|
|
iron.
|
|
</Lede>
|
|
|
|
<Box
|
|
render={<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.15 }} />}
|
|
mt={32}
|
|
>
|
|
<XStack flexWrap="wrap" items="center" justify="center" gap={12}>
|
|
<Action href="#apply" fill>
|
|
Apply for credits
|
|
<ArrowRight width={16} height={16} />
|
|
</Action>
|
|
<Action href="https://platform.hanzo.ai" borderColor="var(--border)">
|
|
Start building free
|
|
</Action>
|
|
</XStack>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Pillars */}
|
|
<Box render="section" pb={32} px={16} $md={{ px: 32 }} $lg={{ px: 48 }}>
|
|
<Box width="100%" maxW={1024} mx="auto">
|
|
<Grid columns={TRIO} gap={16}>
|
|
{PILLARS.map(({ icon, title, body }) => (
|
|
<Card key={title} icon={icon} title={title}>
|
|
{body}
|
|
</Card>
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
|
|
{/* Techstars note */}
|
|
<Box width="100%" maxW={1024} mx="auto" mt={16}>
|
|
<XStack
|
|
items="flex-start"
|
|
gap={12}
|
|
p={20}
|
|
rounded="var(--radius-xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg="$hover"
|
|
>
|
|
<View render={<Rocket width={16} height={16} color={mix('var(--foreground)', 70)} />} mt={2} shrink={0} />
|
|
<Line render="p" display="block" fontSize="$2" color={MUTED} lineHeight="var(--leading-relaxed)">
|
|
<Text color="var(--foreground)" fontWeight="500">Techstars founders:</Text>{' '}
|
|
Hanzo is a Techstars company. We offer perks tailored to each
|
|
portfolio brand — mention your batch when you apply and we
|
|
will match it.
|
|
</Line>
|
|
</XStack>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Application form */}
|
|
<Box render="section" id="apply" py={56} px={16} $md={{ px: 32 }} $lg={{ px: 48 }} $platform-web={{ scrollMarginTop: '96px' }}>
|
|
<Box width="100%" maxW={672} mx="auto">
|
|
<Box mb={40} $platform-web={{ textAlign: 'center' }}>
|
|
<Title quiet mb={12}>
|
|
Apply to the program
|
|
</Title>
|
|
<Text render="p" display="block" color={MUTED}>
|
|
We review applications weekly. The more you share, the faster we
|
|
can match you to the right tier.
|
|
</Text>
|
|
</Box>
|
|
|
|
{submitted ? (
|
|
<Box
|
|
render={<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.4 }} />}
|
|
p={40}
|
|
rounded="var(--radius-xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg={mix('var(--primary)', 5)}
|
|
$platform-web={{ textAlign: 'center' }}
|
|
>
|
|
<XStack
|
|
items="center"
|
|
justify="center"
|
|
width={56}
|
|
height={56}
|
|
mx="auto"
|
|
mb={20}
|
|
rounded="var(--radius-full)"
|
|
bg="$raised"
|
|
>
|
|
<CheckCircle2 width={28} height={28} color={mix('var(--foreground)', 70)} />
|
|
</XStack>
|
|
<Title quiet mb={12}>
|
|
Application received
|
|
</Title>
|
|
<Text render="p" display="block" mb={8} color={MUTED}>
|
|
Thanks — we review applications weekly and will be in touch
|
|
by email.
|
|
</Text>
|
|
<Line render="p" display="block" mb={32} {...SM} color={MUTED}>
|
|
Want to start building today? Create an account and pay only
|
|
for what you use — no subscription required.
|
|
</Line>
|
|
<XStack flexWrap="wrap" justify="center" gap={16}>
|
|
<Action href="https://platform.hanzo.ai" fill>
|
|
Start free
|
|
<ArrowRight width={16} height={16} />
|
|
</Action>
|
|
<Action href="/" borderColor="var(--border)">
|
|
Back to Home
|
|
</Action>
|
|
</XStack>
|
|
</Box>
|
|
) : (
|
|
<YStack
|
|
render={<motion.form onSubmit={handleSubmit} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.1 }} />}
|
|
gap={24}
|
|
>
|
|
{/* Company + website */}
|
|
<Grid columns={DUO} gap={16}>
|
|
<Box>
|
|
<Label htmlFor="company" icon={Building2}>
|
|
Company name
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="company"
|
|
name="company"
|
|
value={formData.company}
|
|
onChange={handleChange}
|
|
required
|
|
placeholder="Acme AI"
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<Label htmlFor="website" icon={Globe}>
|
|
Website
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="website"
|
|
name="website"
|
|
value={formData.website}
|
|
onChange={handleChange}
|
|
placeholder="https://acme.ai"
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Contact name + email */}
|
|
<Grid columns={DUO} gap={16}>
|
|
<Box>
|
|
<Label htmlFor="contactName" icon={User}>
|
|
Your name
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="contactName"
|
|
name="contactName"
|
|
value={formData.contactName}
|
|
onChange={handleChange}
|
|
required
|
|
placeholder="Jane Smith"
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<Label htmlFor="email" icon={Mail}>
|
|
Work email
|
|
</Label>
|
|
<Field
|
|
type="email"
|
|
id="email"
|
|
name="email"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
required
|
|
placeholder="[email protected]"
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Role + stage */}
|
|
<Grid columns={DUO} gap={16}>
|
|
<Box>
|
|
<Label htmlFor="role" icon={Briefcase}>
|
|
Your role
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="role"
|
|
name="role"
|
|
value={formData.role}
|
|
onChange={handleChange}
|
|
placeholder="Co-founder & CEO"
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<Label htmlFor="stage" icon={Rocket}>
|
|
Company stage
|
|
</Label>
|
|
<Choose
|
|
id="stage"
|
|
name="stage"
|
|
value={formData.stage}
|
|
onChange={handleChange}
|
|
required
|
|
>
|
|
<option value="" disabled>
|
|
Select stage
|
|
</option>
|
|
{STAGES.map((s) => (
|
|
<option key={s.id} value={s.id}>
|
|
{s.label}
|
|
</option>
|
|
))}
|
|
</Choose>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Investors free text */}
|
|
<Box>
|
|
<Label htmlFor="investors" icon={Banknote}>
|
|
Who are your investors?{' '}
|
|
<Aside>(funds, angels)</Aside>
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="investors"
|
|
name="investors"
|
|
value={formData.investors}
|
|
onChange={handleChange}
|
|
placeholder="e.g. Sequoia, plus a few angels"
|
|
/>
|
|
</Box>
|
|
|
|
{/* Tier-1 checkboxes */}
|
|
<Box>
|
|
<Label icon={CheckCircle2}>
|
|
Backed by any of these?{' '}
|
|
<Aside>
|
|
(tier-1 backing qualifies you for the credits program)
|
|
</Aside>
|
|
</Label>
|
|
<Grid columns={DUO} gap={8}>
|
|
{TIER1_FUNDS.map((fund) => (
|
|
<Toggle
|
|
key={fund}
|
|
on={formData.tier1Investors.includes(fund)}
|
|
onClick={() => toggleInArray('tier1Investors', fund)}
|
|
align="center"
|
|
pad={10}
|
|
>
|
|
<Line {...SM} color="var(--foreground)">{fund}</Line>
|
|
</Toggle>
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
|
|
{/* Raised + team size */}
|
|
<Grid columns={DUO} gap={16}>
|
|
<Box>
|
|
<Label htmlFor="amountRaised" icon={Banknote}>
|
|
Amount raised{' '}
|
|
<Aside>(optional)</Aside>
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="amountRaised"
|
|
name="amountRaised"
|
|
value={formData.amountRaised}
|
|
onChange={handleChange}
|
|
placeholder="$2M"
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<Label htmlFor="teamSize" icon={Users}>
|
|
Team size
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="teamSize"
|
|
name="teamSize"
|
|
value={formData.teamSize}
|
|
onChange={handleChange}
|
|
placeholder="5"
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Building */}
|
|
<Box>
|
|
<Label htmlFor="building" icon={Boxes}>
|
|
What are you building?
|
|
</Label>
|
|
<Note
|
|
id="building"
|
|
name="building"
|
|
value={formData.building}
|
|
onChange={handleChange}
|
|
required
|
|
rows={4}
|
|
placeholder="Describe your product, who it is for, and where you are today..."
|
|
/>
|
|
</Box>
|
|
|
|
{/* Use cases */}
|
|
<Box>
|
|
<Label icon={Cpu}>
|
|
How will you use Hanzo?
|
|
</Label>
|
|
<Grid columns={DUO} gap={8}>
|
|
{USE_CASES.map((uc) => (
|
|
<Toggle
|
|
key={uc.id}
|
|
on={formData.useCases.includes(uc.id)}
|
|
onClick={() => toggleInArray('useCases', uc.id)}
|
|
align="flex-start"
|
|
pad={12}
|
|
>
|
|
<span>
|
|
<Line {...SM} fontWeight="500" color="var(--foreground)">{uc.label}</Line>
|
|
<Line render="p" display="block" mt={2} {...XS} color={MUTED}>{uc.description}</Line>
|
|
</span>
|
|
</Toggle>
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
|
|
{/* Infra spend */}
|
|
<Box>
|
|
<Label htmlFor="infraSpend" icon={Banknote}>
|
|
Current infra / compute spend per month{' '}
|
|
<Aside>(optional)</Aside>
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="infraSpend"
|
|
name="infraSpend"
|
|
value={formData.infraSpend}
|
|
onChange={handleChange}
|
|
placeholder="$3,000 / mo"
|
|
/>
|
|
</Box>
|
|
|
|
{/* BYO compute */}
|
|
<YStack
|
|
gap={16}
|
|
p={20}
|
|
rounded="var(--radius-xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg="$panel"
|
|
>
|
|
<XStack
|
|
render={<button type="button" onClick={() => setFormData((p) => ({ ...p, byoCompute: !p.byoCompute }))} />}
|
|
items="flex-start"
|
|
gap={12}
|
|
width="100%"
|
|
$platform-web={{ textAlign: 'left' }}
|
|
>
|
|
<Check on={formData.byoCompute} size={20} mt={2} />
|
|
<span>
|
|
<Line display="flex" items="center" gap={8} {...SM} fontWeight="500" color="var(--foreground)">
|
|
<Cpu width={14} height={14} color={MUTED} />
|
|
I want to bring my own compute
|
|
</Line>
|
|
<Line display="block" mt={4} {...XS} color={MUTED}>
|
|
Connect your own GPUs or machines and run on hardware you
|
|
already own.
|
|
</Line>
|
|
</span>
|
|
</XStack>
|
|
{formData.byoCompute && (
|
|
<Box>
|
|
<Line render="label" htmlFor="byoHardware" display="block" mb={8} {...XS} fontWeight="500" color={MUTED}>
|
|
What hardware do you have?
|
|
</Line>
|
|
<Field
|
|
type="text"
|
|
id="byoHardware"
|
|
name="byoHardware"
|
|
value={formData.byoHardware}
|
|
onChange={handleChange}
|
|
placeholder="e.g. 8x H100, a few Mac Studios, on-prem cluster"
|
|
/>
|
|
</Box>
|
|
)}
|
|
</YStack>
|
|
|
|
{/* Techstars + heard */}
|
|
<Grid columns={DUO} gap={16}>
|
|
<Box>
|
|
<Label htmlFor="techstarsBatch" icon={Rocket}>
|
|
Techstars affiliation{' '}
|
|
<Aside>(optional)</Aside>
|
|
</Label>
|
|
<Field
|
|
type="text"
|
|
id="techstarsBatch"
|
|
name="techstarsBatch"
|
|
value={formData.techstarsBatch}
|
|
onChange={handleChange}
|
|
placeholder="Batch / program, e.g. NYC W17"
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<Label htmlFor="heardVia" icon={Globe}>
|
|
How did you hear about us?
|
|
</Label>
|
|
<Choose
|
|
id="heardVia"
|
|
name="heardVia"
|
|
value={formData.heardVia}
|
|
onChange={handleChange}
|
|
>
|
|
<option value="">Select one</option>
|
|
{HEARD_OPTIONS.map((h) => (
|
|
<option key={h} value={h}>
|
|
{h}
|
|
</option>
|
|
))}
|
|
</Choose>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Honeypot: visually hidden, off-screen, not tab-reachable. */}
|
|
<Box aria-hidden="true" position="absolute" l={-9999} t={-9999} width={0} height={0} overflow="hidden">
|
|
<label htmlFor="hp">Do not fill this in</label>
|
|
<input
|
|
type="text"
|
|
id="hp"
|
|
name="hp"
|
|
value={formData.hp}
|
|
onChange={handleChange}
|
|
tabIndex={-1}
|
|
autoComplete="off"
|
|
/>
|
|
</Box>
|
|
|
|
{/* Error */}
|
|
{error && (
|
|
<Line
|
|
render="div"
|
|
display="block"
|
|
p={16}
|
|
rounded="var(--radius-lg)"
|
|
borderWidth={1}
|
|
fontSize="$2"
|
|
color="var(--state-error-text)"
|
|
lineHeight="1.4286" borderColor={mix('var(--state-error)', 30)}
|
|
bg={mix('var(--state-error)', 10)}
|
|
>
|
|
{error}
|
|
</Line>
|
|
)}
|
|
|
|
{/* Submit.
|
|
`alignItems` is what `w-full sm:w-auto` was asking for: the
|
|
control stretches on a phone and shrinks to its words from
|
|
640, which is a property of the COLUMN and not of the button. */}
|
|
<YStack pt={8} items="stretch" $sm={{ items: 'flex-start' }}>
|
|
<Action
|
|
render="button"
|
|
type="submit"
|
|
disabled={isSubmitting}
|
|
fill
|
|
justify="center"
|
|
opacity={isSubmitting ? 0.5 : undefined}
|
|
>
|
|
{isSubmitting ? (
|
|
'Submitting...'
|
|
) : (
|
|
<>
|
|
<Send width={16} height={16} />
|
|
Submit application
|
|
</>
|
|
)}
|
|
</Action>
|
|
</YStack>
|
|
|
|
<Line render="p" display="block" fontSize="$1" lineHeight="1.3333" color={mix('var(--muted-foreground)', 60)}>
|
|
By submitting, you agree to Hanzo’s{' '}
|
|
{/* Nothing of its own: @hanzo/design's base underlines a link
|
|
inside a paragraph on approach, which is this site's whole
|
|
treatment for a link in a sentence. */}
|
|
<Link href="/legal/privacy">
|
|
Privacy Policy
|
|
</Link>
|
|
. We use your information only to review this application.
|
|
</Line>
|
|
</YStack>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Fallback CTA for everyone */}
|
|
<Box render="section" pb={96} px={16} $md={{ px: 32 }} $lg={{ px: 48 }}>
|
|
<Box
|
|
width="100%"
|
|
maxW={768}
|
|
mx="auto"
|
|
p={32}
|
|
$sm={{ p: 40 }}
|
|
rounded="var(--radius-2xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg="$panel"
|
|
$platform-web={{ textAlign: 'center' }}
|
|
>
|
|
<Title quiet mb={12}>
|
|
Not venture-backed yet?
|
|
</Title>
|
|
<Text render="p" display="block" mb={24} width="100%" maxW={576} mx="auto" color={MUTED}>
|
|
You do not need to qualify to build. Create an account, pay only for
|
|
what you use, and apply for credits when you raise.
|
|
</Text>
|
|
<Action href="https://platform.hanzo.ai" fill>
|
|
Start free
|
|
<ArrowRight width={16} height={16} />
|
|
</Action>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|