Files
zeekay 3c0491838b /iam and /s3 open on two sentences and a list of facts, not fifteen centred lines
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.
2026-10-18 17:09:33 -07:00

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