mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Code on hanzo.ai was plain monospace: 285 blocks across 200 pages, one of them hand-inked in four greys. components/code now renders each sample at build with shiki and @hanzo/ui's codeTheme (Dracula dark, GitHub Light light), every colour written as light-dark() so the page's color-scheme picks it, on the theme's own ground. The page ships the coloured spans and no highlighter; e2e/gates/code.spec.ts holds both. A client page that shows code is split into a server page.tsx that owns the sample and a client view.tsx that takes the highlighted node: ProductLanding's code.source, the blockchain pages' codeExamples, the models kit Sample, the guest-key card, realtime, analytics, payments, sdks and the rest. Transcripts, agent prompts and ASCII diagrams are not code and keep their markup. Takes @hanzo/ui 8.27.83 for codeTheme and @hanzo/design 0.5.35 with it, regenerates app/gui.css for the new theme keys, and lowers the raw-colour allowance to 58: the samples' literal inks are gone.
185 lines
9.6 KiB
TypeScript
185 lines
9.6 KiB
TypeScript
"use client"
|
|
|
|
import type { ReactNode } from 'react'
|
|
|
|
/**
|
|
* /treasury — the double-entry ledger, what it is made of, and how a
|
|
* transaction is written.
|
|
*
|
|
* NO TAILWIND. The three sections under the hero are
|
|
* `components/marketing/band`'s `Band` and `Head`; the type is @hanzo/ui's
|
|
* marketing components (`Display`, `Claim`, `Lede`, `Title`, `Chip`, `Action`,
|
|
* `Leaf`, `Line`); the grids are `@hanzo/ui/grid`'s `Grid`; every frame is gui
|
|
* props. A `color-mix()` ink is not a value gui's colour props type, so a
|
|
* frame's tint rides `$platform-web`, which gui still writes as a class.
|
|
*
|
|
* One thing to know before editing the hero: the wash behind it is animated by
|
|
* framer, which owns `transform` for the length of the animation. Its centring
|
|
* is therefore the `translate` PROPERTY — which is also what Tailwind v4's
|
|
* `-translate-x-1/2` set, so nothing moved — and writing it as a
|
|
* `transform: translate(...)` would be overwritten by the `scale` keyframe on
|
|
* the first frame.
|
|
*/
|
|
|
|
import { motion } from "@/components/motion"
|
|
import { ProductFooter } from "@/components/products/ProductFooter"
|
|
import {
|
|
ArrowRight,
|
|
Banknote,
|
|
GitMerge,
|
|
Landmark,
|
|
RefreshCw,
|
|
Shield,
|
|
Wallet,
|
|
} from "lucide-react"
|
|
import { Box, XStack } from '@hanzo/ui'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import { Band, Head } from '@/components/marketing/band'
|
|
import { Action, Chip, Claim, Display, Lede, Title, Leaf, Line } from '@hanzo/ui/marketing'
|
|
|
|
/** The inks this page draws in. */
|
|
const INK = 'var(--foreground)'
|
|
const DIM = 'var(--muted-foreground)'
|
|
const SOFT = 'color-mix(in oklab, var(--foreground) 80%, transparent)'
|
|
|
|
const SMALL = { fontSize: '$2', lineHeight: '1.4286', color: DIM } as const
|
|
const STAT = { fontSize: '$7', lineHeight: '1.3333', fontWeight: '700', color: INK } as const
|
|
|
|
/** The hairline tile a stat or a feature sits in. */
|
|
const TILE = {
|
|
p: 16,
|
|
rounded: 'var(--radius-xl)',
|
|
borderWidth: 1,
|
|
borderColor: 'var(--border)',
|
|
'$platform-web': { backgroundColor: 'color-mix(in oklab, var(--secondary) 50%, transparent)' },
|
|
} as const
|
|
|
|
/** A feature card: the same tile as a `Leaf`, so its edge answers a pointer. */
|
|
const FEATURE = { p: 24, borderColor: "var(--border)", bg: "color-mix(in oklab, var(--secondary) 50%, transparent)", hoverStyle: { borderColor: "var(--neutral-600)" } } as const
|
|
|
|
/** The 48px disc a feature's mark sits on. */
|
|
const DISC = {
|
|
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 controls under the hero, wrapping on a phone. */
|
|
const WRAP = { flexWrap: 'wrap', justify: 'center', gap: 16 } as const
|
|
|
|
/** The pair that closes the page: stacked on a phone, side by side from 640.
|
|
* PROPS, so the `$sm` row is one atomic class beside the base column. */
|
|
const ROW = { display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 12 } as const
|
|
|
|
const features = [
|
|
{ icon: Banknote, title: "Double-entry ledger", description: "A transaction is a set of postings with a source and a destination, and it is rejected unless it balances. Entries are immutable — a correction is another transaction, so history stays true." },
|
|
{ icon: GitMerge, title: "Reconciliation", description: "Match what the ledger believes against what a payment provider reports, and show the rows that disagree. The breaks are the output; agreeing totals need no attention." },
|
|
{ icon: Wallet, title: "Accounts and balances", description: "Hold balances per account and per currency, and move between them as ledger transactions. What an account settles against is configured per deployment." },
|
|
{ icon: RefreshCw, title: "Wallets with holds", description: "Multi-currency virtual wallets that can hold an amount and release it later. That is how you take money at checkout and pay a seller on delivery without the balance lying in between." },
|
|
{ icon: Shield, title: "Flows and webhooks", description: "Longer sequences — capture, split, pay out, retry — run as orchestrated flows, and every change of financial state is delivered as an event you can subscribe to." },
|
|
]
|
|
|
|
export default function TreasuryPage({ split }: { split: ReactNode }) {
|
|
return (
|
|
<>
|
|
<Box
|
|
render="section"
|
|
position="relative"
|
|
minH="90vh"
|
|
display="flex"
|
|
items="center"
|
|
justify="center"
|
|
overflow="hidden"
|
|
py="$11"
|
|
px={16}
|
|
$sm={{ px: 24 }}
|
|
$lg={{ px: 32 }}
|
|
>
|
|
|
|
<Box position="relative" z="var(--z-raised)" width="100%" maxW={1024} mx="auto" $platform-web={{ textAlign: 'center' }}>
|
|
<Chip render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} />} mb={32} px={16} py={8} fontWeight="500" borderColor="var(--border)" bg="color-mix(in oklab, var(--pure-white) 5%, transparent)" color={SOFT}>
|
|
<Landmark width={16} height={16} color={INK} />
|
|
Treasury Operations
|
|
</Chip>
|
|
<Display render={<motion.h1 initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.1 }} />} mb={24}>
|
|
Hanzo{" "}
|
|
Treasury
|
|
</Display>
|
|
<Claim render={<motion.p initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.15 }} />} mb={16} fontWeight="500">
|
|
A double-entry ledger for money moving inside your product
|
|
</Claim>
|
|
<Lede render={<motion.p initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.2 }} />} mb={32} width="100%" maxW={768} mx="auto">
|
|
Every movement is postings between accounts, so the books balance by construction
|
|
rather than by a nightly job that hopes. Marketplace splits, held funds, refunds
|
|
and fees are written as transactions you can replay — which is what you need the
|
|
day someone asks where a particular dollar went.
|
|
</Lede>
|
|
<Box render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.25 }} />} mb={48} width="100%" maxW={768} mx="auto">
|
|
<Grid columns={2} gap={16}>
|
|
<Box {...TILE}><Line render="div" display="block" {...STAT}>Auto</Line><Line render="div" display="block" {...SMALL}>Sweep</Line></Box>
|
|
<Box {...TILE}><Line render="div" display="block" {...STAT}>Real-time</Line><Line render="div" display="block" {...SMALL}>Recon</Line></Box>
|
|
</Grid>
|
|
</Box>
|
|
<XStack render={<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.3 }} />} {...WRAP}>
|
|
<Action href="https://docs.hanzo.ai/docs/cli/treasury" fill>Read the docs <ArrowRight width={16} height={16} /></Action>
|
|
<Action href="https://github.com/hanzoai">GitHub</Action>
|
|
</XStack>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Band pad="$11">
|
|
<Head title="What it is made of" lede="A ledger, a language for writing transactions, and the machinery around them." />
|
|
<Grid columns={{ min: 300, max: 3 }} gap={24}>
|
|
{features.map((feature, index) => (
|
|
<Leaf render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: index * 0.05 }} />} key={feature.title} {...FEATURE} hoverStyle={{ borderColor: "var(--neutral-600)" }}>
|
|
<XStack mb={16} {...DISC}><feature.icon width={24} height={24} color={INK} /></XStack>
|
|
<Line render="h3" display="block" mb={8} fontSize="$4" lineHeight="1.5556" fontWeight="600" color={INK}>{feature.title}</Line>
|
|
<Line render="p" display="block" {...SMALL}>{feature.description}</Line>
|
|
</Leaf>
|
|
))}
|
|
</Grid>
|
|
</Band>
|
|
|
|
<Band pad="$11" measure={896}>
|
|
<Head title="Write the transaction, not the bookkeeping" />
|
|
<Box
|
|
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay: 0.1 }} />}
|
|
overflow="hidden"
|
|
rounded="var(--radius-xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg="var(--secondary)"
|
|
>
|
|
<XStack items="center" gap={8} px={16} py={8} borderBottomWidth={1} borderColor="var(--border)">
|
|
<XStack gap={6}>
|
|
<Box width={12} height={12} rounded="var(--radius-full)" bg="var(--neutral-700)" />
|
|
<Box width={12} height={12} rounded="var(--radius-full)" bg="var(--neutral-700)" />
|
|
<Box width={12} height={12} rounded="var(--radius-full)" bg="var(--neutral-700)" />
|
|
</XStack>
|
|
<Line ml={8} fontSize="$1" lineHeight="1.3333" color={DIM}>split.num</Line>
|
|
</XStack>
|
|
{split}
|
|
</Box>
|
|
</Band>
|
|
|
|
<Band pad="$10" measure={768}>
|
|
<Box $platform-web={{ textAlign: 'center' }}>
|
|
<Title quiet mb={16}>Post your first transaction</Title>
|
|
<Box {...ROW} $sm={{ flexDirection: 'row' }}>
|
|
<Action href="https://docs.hanzo.ai/docs/cli/treasury" target="_blank" rel="noopener noreferrer" fill justify="center">
|
|
Read the docs <ArrowRight width={16} height={16} />
|
|
</Action>
|
|
<Action href="https://github.com/hanzoai" target="_blank" rel="noopener noreferrer" justify="center">
|
|
View on GitHub
|
|
</Action>
|
|
</Box>
|
|
<ProductFooter slug="treasury" name="Treasury" />
|
|
</Box>
|
|
</Band>
|
|
</>
|
|
)
|
|
}
|