mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Both pages are a heading and one paragraph, and the paragraphs had grown to 230 and 140 words set centred under the hero: PKCE, argon2id, refresh families, SCIM and ML-DSA-65 in one block on /iam, shards and roles on /s3. The products Lede takes `facts` now, one per line under a hairline, left-aligned on the paragraph's measure, and the paragraph says only what the product is. Every fact is the old paragraph's, reworded where a sentence had to stand alone.
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
import { ArrowRight } from 'lucide-react'
|
|
import * as ui from '@hanzo/ui/marketing'
|
|
import { Text, YStack, XStack } from '@/components/ui'
|
|
import { Action, Chip, Display } from '@hanzo/ui/marketing'
|
|
import type { CTA } from '@/components/product/ProductLanding'
|
|
|
|
/**
|
|
* A product stated in one heading and one paragraph, over the house glow.
|
|
*
|
|
* Seven pages open this way. Three of them — /iam, /llm, /s3 — are ONLY this
|
|
* section and the links under it; four more put a badge above the heading and
|
|
* two buttons below it. The badge and the buttons are therefore optional
|
|
* rather than a second hero component: the difference between the two groups
|
|
* is what they have to say, not how a heading is set.
|
|
*
|
|
* The title is a node rather than a string because one of the seven counts the
|
|
* models in its own heading.
|
|
*
|
|
* FOUR COMPONENTS, NOT FOUR STACKS OF UTILITIES. The heading is @hanzo/ui's
|
|
* `Display`, the paragraph under it `Lede`, the badge above it `Chip` and the
|
|
* two controls `Action` — the same four every page on the site sets its copy
|
|
* in. Seven pages were reading their type off this file instead, which is how
|
|
* the display heading came to be set at a weight and a leading nothing else on
|
|
* the site uses.
|
|
*/
|
|
/**
|
|
* The badge's own dials: `Chip` leaves padding and fill to its caller.
|
|
*
|
|
* `alignSelf` because the block it sits in is now a gui stack. An inline-flex
|
|
* badge inside a `text-align: center` BLOCK centres itself, which is how this
|
|
* has always worked; inside a flex column it is an item, and an item with no
|
|
* alignment of its own stretches the full measure.
|
|
*/
|
|
const BADGE = { self: "center", mb: 32, px: 16, py: 8, bg: "color-mix(in oklab, var(--pure-white) 5%, transparent)" } as const
|
|
|
|
export function Lede({
|
|
title,
|
|
badge,
|
|
badgeIcon: Badge,
|
|
actions,
|
|
facts,
|
|
children,
|
|
}: {
|
|
title: ReactNode
|
|
badge?: string
|
|
badgeIcon?: LucideIcon
|
|
actions?: CTA[]
|
|
/**
|
|
* What the product does, one fact a line, under the paragraph. A page that
|
|
* has more to say than two sentences says it here rather than in the
|
|
* paragraph: /iam's and /s3's ledes had grown to fifteen centred lines.
|
|
*/
|
|
facts?: readonly string[]
|
|
children: ReactNode
|
|
}) {
|
|
return (
|
|
<YStack
|
|
render="section"
|
|
position="relative"
|
|
overflow="hidden"
|
|
pt={128}
|
|
pb={80}
|
|
px={16}
|
|
$md={{ px: 32 }}
|
|
$lg={{ px: 48 }}
|
|
>
|
|
|
|
<YStack
|
|
position="relative"
|
|
z="var(--z-raised)"
|
|
width="100%"
|
|
maxW={896}
|
|
mx="auto"
|
|
$platform-web={{ textAlign: 'center' }}
|
|
>
|
|
{/* An inline-level badge, centred by the block's own `text-align`, which
|
|
is how it has always been centred. */}
|
|
{badge && (
|
|
<Chip {...BADGE}>
|
|
{Badge && <Badge width={16} height={16} color="var(--pure-white)" />}
|
|
<Text fontWeight="500" color="var(--text-secondary)">{badge}</Text>
|
|
</Chip>
|
|
)}
|
|
<Display mb={24}>
|
|
{title}
|
|
</Display>
|
|
<ui.Lede maxW={672} mx="auto" mb={32}>
|
|
{children}
|
|
</ui.Lede>
|
|
{actions && actions.length > 0 && (
|
|
<XStack flexWrap="wrap" justify="center" gap={16}>
|
|
{actions.map((a, i) => {
|
|
const Icon = a.icon ?? (i === 0 ? ArrowRight : undefined)
|
|
return (
|
|
<Action key={a.label} href={a.href} fill={i === 0}>
|
|
{a.label} {Icon && <Icon width={16} height={16} />}
|
|
</Action>
|
|
)
|
|
})}
|
|
</XStack>
|
|
)}
|
|
{facts && facts.length > 0 && (
|
|
<YStack render="ul" width="100%" maxW={672} mx="auto" mt={16} p={0} $platform-web={{ textAlign: 'left', listStyle: 'none' }}>
|
|
{facts.map((f) => (
|
|
<Text key={f} render="li" display="block" py={14} borderTopWidth={1} borderColor="var(--border)" fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
|
|
{f}
|
|
</Text>
|
|
))}
|
|
</YStack>
|
|
)}
|
|
</YStack>
|
|
</YStack>
|
|
)
|
|
}
|
|
|
|
export default Lede
|