Files
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
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.
2026-10-22 19:07:20 -07:00

626 lines
26 KiB
TypeScript

"use client"
/**
* /careers — the openings, the terms, and how hiring runs.
*
* 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`, `Leaf`, `Line`); everything
* left is gui props over @hanzo/design's tokens.
*/
import { useState, useEffect, type FormEvent } from "react"
import { motion } from "@/components/motion"
import {
Briefcase,
Globe,
Clock,
MapPin,
Users,
Sparkles,
ArrowRight,
Heart,
CheckCircle2,
X,
Share2,
Check,
ExternalLink,
} from "lucide-react"
import { Box, Button, Dialog, DialogContent, DialogTitle, Input, Text, Textarea, View, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Band, Head } from '@/components/marketing/band'
import { Action, Chip, Display, Lede, Title, Leaf, Lift, Line, type LineProps } from '@hanzo/ui/marketing'
import { api } from '@hanzo/app/lib/api'
import { track } from '@hanzo/app/lib/tags'
import { jobOpenings, getJobOpening, type JobOpening } from '@/lib/careers'
import { ResumeAndConsent, resumeProblem, uploadResume } from './apply'
/** The two inks this page draws body copy in. */
const INK = 'var(--foreground)'
const DIM = 'var(--muted-foreground)'
/** The pair of controls that closes a section, wrapping on a phone. */
const ROW = { flexWrap: 'wrap', items: 'center', justify: 'center', gap: 16 } as const
/** The hairline tile, centred. */
const CARD = { rounded: 'var(--radius-xl)', borderWidth: 1, borderColor: 'var(--border)', bg: 'var(--secondary)', p: 24 } as const
/** A card's heading and the paragraph under it. */
const CardTitle = (p: LineProps) => (
<Line render="h3" display="block" mb={8} fontSize="$4" lineHeight="1.5556" fontWeight="600" color={INK} {...p} />
)
const CardBody = (p: LineProps) => (
<Line render="p" display="block" fontSize="$2" lineHeight="1.4286" color={DIM} {...p} />
)
/** A job card's own frame: `Leaf` with its edge drawn at rest and 24px inside. */
const OPENING = { p: 24, borderColor: "var(--border)", bg: "var(--secondary)" } as const
/** An icon on a tinted tile — the mark that opens a card. */
const TILE = {
width: 48,
height: 48,
items: 'center',
justify: 'center',
rounded: 'var(--radius-lg)',
'$platform-web': { backgroundColor: 'color-mix(in oklab, var(--pure-white) 10%, transparent)' },
} as const
/** The one fact line beside a job: a mark and a word. */
const Fact = (p: LineProps) => (
<Line display="inline-flex" items="center" gap={4} whiteSpace="normal" fontSize="$2" lineHeight="1.4286" color={DIM} {...p} />
)
const benefits = [
{ icon: Globe, title: "Remote-first", description: "The team is distributed. Work from wherever you already live." },
{ icon: Clock, title: "Your own hours", description: "We care what you ship, not what time you were online." },
{ icon: Users, title: "Small team", description: "Few enough people that you own whole systems rather than tickets." },
{ icon: Sparkles, title: "The work is public", description: "Most of what we build is open source, under your own name." },
]
const hiringSteps = [
{ step: "1", title: "Application Review", description: "We review your resume and application materials" },
{ step: "2", title: "Initial Interview", description: "Get to know you and your experience" },
{ step: "3", title: "Technical Assessment", description: "Showcase your skills (no whiteboarding)" },
{ step: "4", title: "Final Interview", description: "Meet the team and discuss next steps" },
]
/** An address as https: one typed without a scheme gets https://, and cloud drops any other. */
const https = (u: string): string => {
const t = u.trim()
return !t || /^[a-z][a-z0-9+.-]*:/i.test(t) ? t : `https://${t}`
}
export default function CareersPage() {
const [activeJob, setActiveJob] = useState<JobOpening | null>(null)
const [copiedId, setCopiedId] = useState<string | null>(null)
const [form, setForm] = useState({
name: '',
email: '',
github: '',
linkedin: '',
location: '',
resume: '',
notes: '',
})
const [file, setFile] = useState<File | null>(null)
const [consent, setConsent] = useState(false)
const [busy, setBusy] = useState(false)
const [sent, setSent] = useState(false)
const [wrong, setWrong] = useState<string | null>(null)
useEffect(() => {
if (typeof window === 'undefined') return
const params = new URLSearchParams(window.location.search)
const applyId = params.get('apply')
if (applyId) {
const match = getJobOpening(applyId)
if (match) setActiveJob(match)
}
}, [])
const openApply = (job: JobOpening) => {
setActiveJob(job)
setSent(false)
setWrong(null)
if (typeof window !== 'undefined') {
history.pushState(null, '', `?apply=${job.id}`)
}
}
const closeApply = () => {
setActiveJob(null)
setSent(false)
setWrong(null)
if (typeof window !== 'undefined') {
const url = new URL(window.location.href)
url.searchParams.delete('apply')
history.pushState(null, '', url.pathname + (url.hash || ''))
}
}
const copyRoleLink = (jobId: string, e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (typeof window !== 'undefined') {
const origin = window.location.origin
navigator.clipboard.writeText(`${origin}/careers/${jobId}`)
setCopiedId(jobId)
setTimeout(() => setCopiedId(null), 2000)
}
}
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
if (busy) return
if (!form.name.trim() || !form.email.trim() || !form.notes.trim()) {
setWrong('Please fill in your name, email, and cover note / background.')
return
}
const EMAIL_TEST = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!EMAIL_TEST.test(form.email.trim())) {
setWrong('Please enter a valid email address.')
return
}
if (file && resumeProblem(file)) {
setWrong(resumeProblem(file))
return
}
setWrong(null)
setBusy(true)
let resumeFile = ''
try {
if (file) resumeFile = await uploadResume(file)
} catch {
setWrong('Your resume did not upload. Please try again, or give a link instead.')
setBusy(false)
return
}
const payload = {
name: form.name.trim(),
email: form.email.trim(),
role: activeJob?.title || 'Open Application',
department: activeJob?.department || 'General',
country: form.location.trim() || activeJob?.location || 'Remote',
source: `hanzo.ai/careers/${activeJob?.id || 'open'}`,
// An application to an opening is a candidate in the CRM (cloud apps/marketing
// leads.go `job`): what the person wrote, their resume and their links, with no
// call offered and nothing sent to the ad platforms.
job: activeJob?.id || 'open',
need: form.notes.trim(),
resume: https(form.resume),
links: [https(form.github), https(form.linkedin)].filter(Boolean),
resume_file: resumeFile,
consent,
}
try {
const r = await fetch(`${api()}/v1/marketing/leads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!r.ok) throw new Error(`HTTP ${r.status}`)
track('career_application_submitted', { role: payload.role, department: payload.department, slug: activeJob?.id })
setSent(true)
} catch (err) {
track('career_application_submitted', { role: payload.role, department: payload.department, slug: activeJob?.id, error: String(err) })
setWrong('Your application did not reach us. Please try again, or email it to [email protected].')
} finally {
setBusy(false)
}
}
return (
<Box minH="100vh" $platform-web={{ color: INK }}>
<main>
{/* Hero Section */}
<Box
render="section"
position="relative"
overflow="hidden"
pt={96}
pb={64}
px={16}
$md={{ px: 32 }}
$lg={{ px: 48 }}
>
<View
aria-hidden
position="absolute"
t={0}
l="50%"
x="-50%"
width={800}
height={400}
backgroundImage="radial-gradient(circle, color-mix(in srgb, var(--primary) 12%, transparent) 0%, transparent 70%)"
filter="blur(80px)"
pointerEvents="none"
/>
<Box position="relative" z="var(--z-raised)" width="100%" maxW={1024} mx="auto">
<Box $platform-web={{ textAlign: 'center' }}>
<Chip render={<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} />} mb={24} px={12} py={4} fontSize="$1" lineHeight="1.3333" fontWeight="500" borderColor="var(--border)" bg="color-mix(in oklab, var(--pure-white) 20%, transparent)" color={INK}>
<Heart width={14} height={14} />
Careers
</Chip>
<Display render={<motion.h1 initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.05 }} />} mb={24}>
Compilers, cryptography,<br />
and the cloud they run on.
</Display>
<Lede render={<motion.p initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.1 }} />} mb={40} width="100%" maxW={768} mx="auto">
Hanzo is a small team building an open AI cloud — the inference engine, the models, the backend services, and the agents that use them. Most of what we write is public, so you can read the codebase before you apply.
</Lede>
<XStack render={<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.15 }} />} {...ROW}>
<Action href="#open-positions" fill>
View Open Positions
<ArrowRight width={16} height={16} />
</Action>
<Action href="/about">
Read the history
</Action>
</XStack>
</Box>
</Box>
</Box>
{/* Benefits Section */}
<Band rule={false} pad="$10" measure={1152} ground="color-mix(in oklab, var(--secondary) 20%, transparent)">
<Head title="Benefits & Perks" lede="How the team actually works." />
<Grid columns={{ min: 260, max: 4 }} gap={24}>
{benefits.map((benefit, index) => {
const Icon = benefit.icon
return (
<Box
key={benefit.title}
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: index * 0.1 }} />}
{...CARD}
$platform-web={{ textAlign: 'center' }}
>
<XStack mb={16} mx="auto" {...TILE}>
<Icon width={24} height={24} color={INK} />
</XStack>
<CardTitle>{benefit.title}</CardTitle>
<CardBody>{benefit.description}</CardBody>
</Box>
)
})}
</Grid>
</Band>
{/* Open Positions Section */}
<Band id="open-positions" rule={false} pad="$10" measure={1152}>
<Head title="Open Positions" lede="Remote, unless the work needs hardware in a room." />
<Grid columns={{ min: 320, max: 2 }} gap={24}>
{jobOpenings.map((job, index) => (
<Leaf
id={job.id}
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: index * 0.05 }} />}
key={job.id}
{...OPENING}
>
<XStack items="flex-start" gap={16}>
<XStack shrink={0} {...TILE}>
<Briefcase width={24} height={24} color={INK} />
</XStack>
<YStack grow={1} shrink={1} flexBasis="0%">
<XStack items="center" justify="space-between" mb={8} gap={8}>
<Line href={`/careers/${job.id}`} color="inherit" whiteSpace="inherit">
<Lift render="h3" display="block" fontSize="$6" lineHeight="1.4" fontWeight="700">
{job.title}
</Lift>
</Line>
<Line
render="button"
type="button"
onClick={(e) => copyRoleLink(job.id, e)}
title="Copy direct link to this position"
display="inline-flex"
items="center"
p={4}
color={copiedId === job.id ? 'var(--primary)' : DIM}
>
{copiedId === job.id ? <Check size={16} /> : <Share2 size={16} />}
</Line>
</XStack>
<XStack gap={12} mb={12} flexWrap="wrap">
<Fact><MapPin width={16} height={16} /> {job.location}</Fact>
<Fact><Clock width={16} height={16} /> {job.type}</Fact>
<Fact><Users width={16} height={16} /> {job.department}</Fact>
</XStack>
<CardBody mb={16}>{job.description}</CardBody>
<XStack gap={12} flexWrap="wrap" items="center">
<Action
href={`/careers/${job.id}`}
onClick={(e) => {
e.preventDefault()
openApply(job)
}}
fill
self="flex-start"
>
Apply for this role
<ArrowRight width={16} height={16} />
</Action>
<Action
href={`/careers/${job.id}`}
self="flex-start"
>
View Details
<ExternalLink width={14} height={14} />
</Action>
</XStack>
</YStack>
</XStack>
</Leaf>
))}
</Grid>
<Box render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />} mt={32} $platform-web={{ textAlign: 'center' }}>
<Line render="p" display="block" mb={16} color={DIM}>
Don&apos;t see a position that fits your skills?
</Line>
<Action
href="/careers/open"
onClick={(e) => {
e.preventDefault()
const openRole = getJobOpening('open')
if (openRole) openApply(openRole)
}}
>
Submit Open Application
</Action>
</Box>
</Band>
{/* Hiring Process Section */}
<Band rule={false} pad="$10" measure={896} ground="color-mix(in oklab, var(--secondary) 20%, transparent)">
<Head title="Our Hiring Process" lede="No whiteboarding. Here is the whole thing." />
<Grid columns={{ min: 180, max: 4 }} gap={24}>
{hiringSteps.map((step, index) => (
<Box
key={step.step}
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: index * 0.1 }} />}
$platform-web={{ textAlign: 'center' }}
>
<XStack
mb={16}
mx="auto"
{...TILE}
rounded="var(--radius-full)"
$platform-web={{ backgroundColor: 'color-mix(in oklab, var(--pure-white) 20%, transparent)' }}
>
<Line fontWeight="700" color={INK}>
{step.step}
</Line>
</XStack>
<CardTitle>{step.title}</CardTitle>
<CardBody>{step.description}</CardBody>
</Box>
))}
</Grid>
</Band>
{/* CTA Section */}
<Band rule={false} pad="$12" measure={896}>
<View
position="absolute"
t={-160}
r={-160}
width={320}
height={320}
rounded="var(--radius-full)"
filter="blur(64px)"
pointerEvents="none"
$platform-web={{ backgroundColor: 'color-mix(in oklab, var(--pure-white) 10%, transparent)' }}
/>
<View
position="absolute"
b={-160}
l={-160}
width={320}
height={320}
rounded="var(--radius-full)"
filter="blur(64px)"
pointerEvents="none"
$platform-web={{ backgroundColor: 'color-mix(in oklab, var(--pure-white) 5%, transparent)' }}
/>
<Box position="relative" z="var(--z-raised)" $platform-web={{ textAlign: 'center' }}>
<Title render={<motion.h2 initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />} quiet mb={16}>Ready to join us?</Title>
<Lede render={<motion.p initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.1 }} />} mb={40} width="100%" maxW={672} mx="auto" mt="$3.5">
If you have read the codebase and had opinions about it, we want to hear them.
</Lede>
<XStack render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.2 }} />} {...ROW}>
<Action href="#open-positions" fill>
Browse Open Positions
<ArrowRight width={20} height={20} />
</Action>
<Action href="mailto:[email protected]">
careers@hanzo.ai
</Action>
</XStack>
</Box>
</Band>
<Dialog open={Boolean(activeJob)} onOpenChange={(o) => (!o ? closeApply() : undefined)}>
<DialogContent
showCloseButton={false}
width="calc(100vw - 32px)"
maxW={540}
rounded="$6"
p="$5"
gap="$4"
bg="var(--secondary)"
borderWidth={1}
borderColor="var(--border)"
>
<XStack items="center" justify="space-between" width="100%">
<YStack gap="$1">
<DialogTitle fontSize="$6" fontWeight="700" color={INK}>
{sent ? 'Application Received' : activeJob?.title}
</DialogTitle>
{!sent && activeJob ? (
<Text fontSize="$2" color={DIM}>
{activeJob.department} · {activeJob.location} · {activeJob.type}
</Text>
) : null}
</YStack>
<XStack
render="button"
aria-label="Close"
p="$1.5"
rounded="$3"
hoverStyle={{ bg: 'var(--white-06)' }}
cursor="pointer"
onPress={closeApply}
>
<X size={18} color={INK} />
</XStack>
</XStack>
{sent ? (
<YStack gap="$4" py="$4" items="center">
<CheckCircle2 size={48} color="var(--primary)" />
<YStack gap="$2" items="center">
<Text fontSize="$4" fontWeight="600" color={INK}>
Thank you for applying!
</Text>
<Text fontSize="$2" color={DIM} maxW={420} $platform-web={{ textAlign: 'center' }}>
Your application for <Text fontWeight="600" color={INK}>{activeJob?.title}</Text> has been submitted to our team. We review submissions promptly and will reach out to <Text fontWeight="600" color={INK}>{form.email}</Text>.
</Text>
<Text fontSize="$1" color={DIM} mt="$2" $platform-web={{ textAlign: 'center' }}>
You can also email additional materials or portfolio samples directly to{' '}
<Text render="a" {...{ href: `mailto:[email protected]?subject=${encodeURIComponent(`Application: ${activeJob?.title} - ${form.name}`)}` }} color="var(--primary)" textDecorationLine="underline">
careers@hanzo.ai
</Text>.
</Text>
</YStack>
<Button variant="primary" onPress={closeApply} mt="$2">
Done
</Button>
</YStack>
) : (
<YStack render="form" {...{ onSubmit: handleSubmit }} width="100%" gap={14}>
<YStack gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
Full Name <Text render="span" color="var(--destructive)">*</Text>
</Text>
<Input
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
placeholder="Ada Lovelace"
required
/>
</YStack>
<YStack gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
Work Email <Text render="span" color="var(--destructive)">*</Text>
</Text>
<Input
type="email"
value={form.email}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
placeholder="[email protected]"
required
/>
</YStack>
<XStack gap="$3" flexWrap="wrap">
<YStack flex={1} minW={200} gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
GitHub / Portfolio
</Text>
<Input
value={form.github}
onChange={(e) => setForm((f) => ({ ...f, github: e.target.value }))}
placeholder="https://github.com/..."
/>
</YStack>
<YStack flex={1} minW={200} gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
LinkedIn / Website
</Text>
<Input
value={form.linkedin}
onChange={(e) => setForm((f) => ({ ...f, linkedin: e.target.value }))}
placeholder="https://linkedin.com/in/..."
/>
</YStack>
</XStack>
<YStack gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
Resume link
</Text>
<Input
value={form.resume}
onChange={(e) => setForm((f) => ({ ...f, resume: e.target.value }))}
placeholder="A link to your resume: PDF, Google Drive or Dropbox"
/>
</YStack>
<ResumeAndConsent file={file} onFile={setFile} consent={consent} onConsent={setConsent} />
<YStack gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
Location / Timezone
</Text>
<Input
value={form.location}
onChange={(e) => setForm((f) => ({ ...f, location: e.target.value }))}
placeholder="e.g. San Francisco, CA or Remote (UTC-8)"
/>
</YStack>
<YStack gap="$1">
<Text fontSize="$2" fontWeight="500" color={INK}>
Background & Why Hanzo <Text render="span" color="var(--destructive)">*</Text>
</Text>
<Textarea
value={form.notes}
onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}
placeholder="Briefly tell us what systems you've built, open source work, or what interests you about our cloud and models."
rows={4}
required
/>
</YStack>
{wrong ? (
<Text fontSize="$2" color="var(--destructive)">
{wrong}
</Text>
) : null}
<XStack justify="space-between" items="center" mt="$2">
<Text
render="a"
{...{ href: `mailto:[email protected]?subject=${encodeURIComponent(`Application: ${activeJob?.title}`)}` }}
fontSize="$1"
color="var(--muted-foreground)"
textDecorationLine="underline"
>
Or email resume directly
</Text>
<XStack gap="$2">
<Button variant="ghost" size="sm" type="button" onPress={closeApply}>
Cancel
</Button>
<Button variant="primary" size="sm" type="submit" disabled={busy}>
{busy ? 'Submitting…' : 'Submit Application'}
</Button>
</XStack>
</XStack>
</YStack>
)}
</DialogContent>
</Dialog>
</main>
</Box>
)
}