mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Pages from /machines to /risk, the model pages, and the kits they are made of
(page-kit, ProductLanding, LayerPage, Stack, the models kit, band's Head) mark
authored copy where it is written and draw it in the reader's language. A server
page marks a literal phrase('…') or wraps it in <T>; a client page calls t. The
kits translate a string prop through useSay (components/marketing/say), so a
page passes phrase('…') and markup passes as written. An arrow that points
turns over right to left (data-i18n-mirror).
Rebased on main: the fold-based heroes (Fold, PageHero, MachinesHero, the
product and layer pages) keep main's structure and copy, with their label,
title and lede marked phrase. /platform is main's CloudLanding; the deleted
/platform/layers and the old referral components stay deleted.
Claude-Session: https://claude.ai/code/session_015XYKLjZtDTJEnMo4eK3AFH
92 lines
3.5 KiB
TypeScript
92 lines
3.5 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { Text, YStack, XStack } from '@/components/ui'
|
|
import { Action } from '@hanzo/ui/marketing'
|
|
import { Line } from '@/components/type'
|
|
import type { CTA } from '@/components/product/ProductLanding'
|
|
import { T } from '@hanzo/i18n/react'
|
|
|
|
/**
|
|
* A bordered note: a heading, a paragraph, and the links that go with it.
|
|
*
|
|
* Two different sections on four product pages turn out to be this same shape —
|
|
* the "Built on Open Source" panel naming the upstream project, and the
|
|
* "Need a Custom Solution?" panel under the calls to action. They were written
|
|
* twice per page and four times over, which is eight copies of a box with a
|
|
* heading in it.
|
|
*
|
|
* The body is a node because both of them put a link inside the sentence, and a
|
|
* sentence with a link in it is markup rather than a string. It is a `Text` with
|
|
* its white space put back to `normal`: gui's `Text` is `pre-wrap`, so a sentence
|
|
* a caller authored across three source lines would ship with three hard breaks
|
|
* in it. A heading takes one line and one string, so it is type the site already
|
|
* sets.
|
|
*
|
|
* The links are `Action`, this site's one button shape — the hover is the
|
|
* one thing neither a style attribute nor a gui prop can say.
|
|
*
|
|
* The name lost its `hz-` prefix when this document's rules did, and this call
|
|
* site did not follow. `.hz-action` is declared nowhere, so these links were
|
|
* rendering as bare text; `Start.tsx` carries the measurement.
|
|
*/
|
|
/**
|
|
* The link INSIDE that sentence, brighter than the grey around it.
|
|
*
|
|
* It belongs here rather than beside each caller because it is a fact about
|
|
* this panel's body: three product pages wrote the same pair of utilities on
|
|
* the same link in the same paragraph. Not `More` or `Action` — both are
|
|
* standalone controls with their own box, and stretching one inside a sentence
|
|
* tears the line, which is the rule `app/globals.css` already states for prose.
|
|
*/
|
|
export const LINK = { color: 'var(--foreground)', textDecoration: 'none' } as const
|
|
|
|
/* `title` and each link's label are drawn through <T>: a caller marks the
|
|
literal `phrase('…')` where it writes it. The body is the caller's markup. */
|
|
export function Aside({ title, children, links }: { title: string; children: ReactNode; links?: CTA[] }) {
|
|
return (
|
|
<YStack
|
|
render="section"
|
|
py={64}
|
|
px={16}
|
|
$sm={{ px: 24 }}
|
|
$lg={{ px: 32 }}
|
|
>
|
|
<YStack
|
|
width="100%"
|
|
maxW={768}
|
|
mx="auto"
|
|
p={32}
|
|
borderWidth={1}
|
|
borderColor="var(--edge, var(--white-08))"
|
|
rounded="var(--radius-2xl)"
|
|
bg="$panel"
|
|
>
|
|
<Line render="h3" size="xl" weight="600" marginBottom={12} color="var(--foreground)">
|
|
<T>{title}</T>
|
|
</Line>
|
|
<Line render="p" display="block" fontSize="$2" color="var(--muted-foreground)" whiteSpace="normal" lineHeight="var(--leading-relaxed)">
|
|
{children}
|
|
</Line>
|
|
{links && links.length > 0 && (
|
|
<XStack mt={24} flexWrap="wrap" gap={12}>
|
|
{links.map((l) => {
|
|
const Icon = l.icon
|
|
return (
|
|
<Action
|
|
key={l.label}
|
|
href={l.href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{Icon && <Icon width={16} height={16} />} <T>{l.label}</T>
|
|
</Action>
|
|
)
|
|
})}
|
|
</XStack>
|
|
)}
|
|
</YStack>
|
|
</YStack>
|
|
)
|
|
}
|
|
|
|
export default Aside
|