Files
zeekay bba4e5ca40 A product page ends on one call: the block above the closing keeps the docs and the neighbours and drops its own Get your API key
Every product page ended on "Start building with X" and a filled Get your
API key, then one screen later on the layout's "Build what's next." and a
filled Try Hanzo, in a different button shape: two closing calls a reader
had to choose between. StartBuilding becomes Related: the product's docs
link and its related capabilities, no heading and no button, so the
closing call below is the one way in. And the open-source row no longer
shows a "0 stars" pill, which read as an abandoned repository.
2026-10-18 16:25:16 -07:00

137 lines
5.4 KiB
TypeScript

/**
* /integrations — every SDK, framework and language Hanzo answers as.
*
* NO TAILWIND. This is a SERVER page and stays one. Its own sections take no
* `Band`/`Head`, because `Head` carries a scroll entrance and they have never
* had one — giving them motion would be a change to the picture smuggled in as
* a conversion. The frame is `Box render="section"` with the paddings the page
* already stated; the type is @hanzo/ui's marketing components. The agent prompt
* and the closing block follow the column rather than sit inside it: each is a
* full-bleed `Band` carrying its own rule, gutter and measure, and nesting one
* in this page's 1152 column doubles the gutter and stops the rule short of the
* viewport.
*
* A card is `Leaf`, the site's one card-that-IS-a-link: it owns the resting
* edge, the hover and the reduced-motion opt-out. The rest of the type is
* `Line`, one rung of the ramp per call.
*/
import { INTEGRATIONS, INTEGRATION_CATEGORIES, type Integration } from '@/lib/integrations'
import { Box } from '@/components/ui'
import { Display, Lede, Leaf, Lift, Line, type LineProps } from '@hanzo/ui/marketing'
import { Grid } from '@hanzo/ui/grid'
import { AgentPrompt } from '@/components/product/AgentPrompt'
import { Related } from '@/components/product/Related'
import { product } from '@/lib/product'
import { Code } from '@/components/code'
/** What the agent prompt and the closing block read, off the one table that
* holds a product's name, route, audited command and docs page. */
const PRODUCT = product('integrations')
const CARD = { p: 20, borderColor: "var(--border)", display: "flex", flexDirection: "column", gap: 8, bg: "var(--card)", color: "var(--foreground)", hoverStyle: { borderColor: "color-mix(in oklab, var(--brand) 50%, transparent)" } } as const
/**
* A heading BENEATH the page's own, and under `Title`'s rung: these are a
* card's label and a shelf's label, not a section's argument, so they state
* their size rather than borrowing a role and then overriding two thirds of it.
*/
const Sub = (p: LineProps) => (
<Line render="h2" display="block" fontSize="$4" lineHeight="var(--leading-snug)" fontWeight="600" color="var(--foreground)" {...p} />
)
function IntegrationCard({ integration }: { integration: Integration }) {
return (
<Leaf href={`/integrations/${integration.slug}`} {...CARD} hoverStyle={{ borderColor: "color-mix(in oklab, var(--brand) 50%, transparent)" }}>
<Box display="flex" flexDirection="row" items="center" gap={12}>
<Line fontSize="$7" color="inherit">{integration.icon}</Line>
<Lift fontWeight="600">
{integration.name}
</Lift>
</Box>
{/* Two lines of description, and no more — the card is a fixed size in a grid. */}
<Line render="p" numberOfLines={2} fontSize="$2" color="var(--muted-foreground)">
{integration.description}
</Line>
<Line fontSize="$1" textTransform="capitalize" color="color-mix(in oklab, var(--muted-foreground) 60%, transparent)">
{INTEGRATION_CATEGORIES[integration.category]}
</Line>
</Leaf>
)
}
export default function IntegrationsPage() {
const categories = Object.entries(INTEGRATION_CATEGORIES) as [Integration['category'], string][]
return (
<Box minH="100vh">
<Box
width="100%"
maxW={1152}
mx="auto"
px={16}
py={80}
>
{/* Header */}
<Box mb={64}>
<Display mb={16}>
Works with everything
</Display>
<Lede maxW={672}>
Hanzo AI is fully OpenAI-compatible. Use any SDK, framework, or language you already know.
Drop in{' '}
<Line render="code" fontSize="$2" bg="var(--muted)" px={6} py={2} rounded="var(--radius)" color="inherit">https://api.hanzo.ai/v1</Line>{' '}
as the base URL and your Hanzo API key.
</Lede>
</Box>
{/* Quick start */}
<Box
mb={64}
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="var(--border)"
bg="var(--card)"
p={24}
>
<Sub mb={12}>Quick start — any OpenAI SDK</Sub>
{/* Three clients, one base URL. The comments are `#`, so it reads as Python. */}
<Code
lang="python"
rounded="var(--radius-md)"
code={`# Python
client = OpenAI(base_url="https://api.hanzo.ai/v1", api_key=HANZO_API_KEY)
# TypeScript
const client = new OpenAI({ baseURL: "https://api.hanzo.ai/v1", apiKey: HANZO_API_KEY });
# Go
client := openai.NewClient(option.WithBaseURL("https://api.hanzo.ai/v1"), option.WithAPIKey(HANZO_API_KEY))`}
/>
</Box>
{/* Categories */}
{categories.map(([category, label]) => {
const items = INTEGRATIONS.filter((i) => i.category === category)
if (items.length === 0) return null
return (
<Box key={category} render="section" mb={48}>
<Sub mb={24} fontSize="$7" fontWeight="700">
{label}
</Sub>
<Grid columns={{ min: 280, max: 3 }} gap={16}>
{items.map((integration) => (
<IntegrationCard key={integration.slug} integration={integration} />
))}
</Grid>
</Box>
)
})}
</Box>
{PRODUCT && <AgentPrompt product={PRODUCT} />}
{PRODUCT && <Related product={PRODUCT} />}
</Box>
)
}